RosettaCommons/binder

Uncompile-able results due to namespacing

zwimer opened this issue · 5 comments

TLDR: binder sees the stream operator << in some namespace, B, then tries to define __str__ using << without referencing B.

Consider:

#include <pybind11/pybind11.h>
#include <ostream>

struct A { int a; };

namespace B {
	std::ostream & operator<<(std::ostream & a, const A & b) { return a; }
}

Where a.conf is just +class A. We end up with

// File: unknown/unknown.cpp
#include <ios>
#include <locale>
#include <ostream>
#include <sstream> // __str__
#include <streambuf>
#include <string>

#include <functional>
#include <pybind11/pybind11.h>
#include <string>
#include <pybind11/stl.h>


#ifndef BINDER_PYBIND11_TYPE_CASTER
	#define BINDER_PYBIND11_TYPE_CASTER
	PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>)
	PYBIND11_DECLARE_HOLDER_TYPE(T, T*)
	PYBIND11_MAKE_OPAQUE(std::shared_ptr<void>)
#endif

void bind_unknown_unknown(std::function< pybind11::module &(std::string const &namespace_) > &M)
{
	{ // A file: line:8
		pybind11::class_<A, std::shared_ptr<A>> cl(M(""), "A", "");
		cl.def( pybind11::init( [](){ return new A(); } ) );
		cl.def_readwrite("a", &A::a);

		cl.def("__str__", [](A const &o) -> std::string { std::ostringstream s; s << o; return s.str(); } );
	}
}

If you try to compile this, it will fail because << will not be in scope, since the << operator was defined in the namespace B.

Possible solution: Change binder to be explicit with A::operator<<(s, o) (I like this idea; it is ugly, but this code is autogenerated so that's fine). One other benefit is this would make it clear which << operator is being called in the case that multiple are defined at different scopes.

I'll add this bug to the known bugs PR: #229

Thank you for reporting this @zwimer ! I just pushed 7b47e78 which should fix this.

Hi @lyskov,

7b47e78 causes a new issue for me.

If I add the following to test/T07.class.hpp:

#include <ostream>
namespace operators{
  struct Operators
  {
    friend std::ostream & operator<<(std::ostream & s, const Operators & a) { return s << "Hi"; }
  };
}

, test compilation fails:

/home/ghottiger/git/binder/build/test/T07_class.cpp: In lambda function:
/home/ghottiger/git/binder/build/test/T07_class.cpp:104:105: error: ‘operator<<’ is not a member of ‘operators’; did you mean ‘std::operator<<’?
  104 | ors::Operators const &o) -> std::string { std::ostringstream s; operators::operator<<(s, o); return s.str(); } );
      |                 

Even though this is perfectly ok C++ code. E.g. run this minimal example:

#include <iostream>

namespace operators {
  struct Operators
  {
    friend std::ostream & operator<<(std::ostream & s, const Operators & a) { return s << a.greetings(); }
    std::string greetings() const { return "Hello"; }
  };
}

int main() {
  operators::Operators s;
  std::cout << s << std::endl;
  return 0;
}

@hogabrie ah, - i had encounter this or very similar issue before, it became particularly obvious when class and friend function is templated (issue is not specific to operator<<). Solution that i used is to separate declaration of function from it definition. It is a bit verbose but so far this is the only solution that seems to work. Please try following example, it have worked for me:

namespace aaaa {
struct T
{
    friend std::ostream & operator<<(std::ostream & s, T const &);
};

inline std::ostream & operator<<(std::ostream & s, T const &) { return s << "hi..."; }
}

Thank you @lyskov for the quick support and the amazing binder project. For now I reverted the commit in our fork. When I find the time I will adapt our huge templated library with this proposal to get the bindings working with the latest binder version.