2

I'm trying to define a << operator for a set of classes; the set is open, but all of the members have a common tagging base class, and all have the member function std::string String() const. Basically, what I've got is:

class Tag {};
class Obj : public Tag
{
public:
    std::string String() const { return "specialized"; }
};

template <typename T>
typename std::enable_if<std::is_base_of<Tag, T>::type, std::ostream>::value& operator<<( std::ostream& dest, T const& source)
{
    dest << source.String();
    return dest;
}

int
main()
{
    std::cout << typeid(std::enable_if<std::is_base_of<Tag, Obj>::value, std::ostream>::type).name() << std::endl;
    std::string s( "generic" );
    Obj e;
    std::cout << e << std::endl;
    std::cout << s << std::endl;
    return 0;
}

This doesn't work: with g++ (version 4.8.3, invoked with -std=c++11), I get the error message:

enableIf.cc: In function 'int main()':
enableIf.cc:55:18: error: cannot bind 'std::ostream {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'
     std::cout << e << std::endl;
                  ^
In file included from /usr/lib/gcc/x86_64-pc-cygwin/4.8.3/include/c++/iostream:39:0,
                from enableIf.cc:8:
/usr/lib/gcc/x86_64-pc-cygwin/4.8.3/include/c++/ostream:602:5: error:   initializing argument 1 of 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = Obj]'
     operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
     ^

I can't figure it out, because there aren't any rvalue-references in sight; the compiler seems to have struck on the generic overload for std::ostream&& in the standard library.

With MSC (VS 2013), the error message is a lot more verbose, but it starts with:

enableIf.cc(55) : error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'Obj' (or there is no acceptable conversion)

and then goes on to list a lot of possible functions, all in the standard library.

(In my actual code, line 55 corresponds to the line std::cout << e << std::endl;.)

In both cases, the compiler seems to be rejecting my overloaded function. If I comment out the << lines, however, the code compiles, and the value output by the first line in main seems correct (at least with MSC—the output of g++ is So, what ever that's supposed to mean).

Given that two compilers agree, I assume that there is an error in my code, but I can't figure out what. How do you do this? (FWIW: I'd be equally happy, or even happier, with a solution which generates the overload for all types having a member function std::string Type::String() const.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329

1 Answers1

3

I'm pretty sure you meant this:

template <typename T>                        //  here                 here
typename std::enable_if<std::is_base_of<Tag, T>::type, std::ostream>::value& 
operator<<( std::ostream& dest, T const& source)

to be this:

template <typename T>
typename std::enable_if<std::is_base_of<Tag, T>::value, std::ostream>::type& 
operator<<( std::ostream& dest, T const& source)

after changing as such, you compile successfully.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • @BartekBanachewicz I concur. Personally I would move the method as virtual to `Tag` or simply feature-test the type for it, or the usual `Write(std::ostream&)` we probably all do and just let the compiler sort it out. – WhozCraig Jul 17 '14 at 11:10
  • Thanks. I can't believe I missed this. I originally had `value` everywhere, then caught my error, and replaced it with `type` when appropriate. Except that in one of the expressions, I replaced the wrong one. – James Kanze Jul 17 '14 at 11:15
  • @JamesKanze I lost count how many times I put `type` and `value` in the wrong places when doing SFINAE, so you're not alone. If you didn't see my comment in general, Johannes has done the whole has-member-function work, and it is considerably more complicated than one might think. [Worth reading](http://stackoverflow.com/a/264088/1322972). – WhozCraig Jul 17 '14 at 11:18
  • @WhozCraig Yes. And it's not as if the error messages are very helpful. As usual, another set of eyes sees the problem. – James Kanze Jul 17 '14 at 11:19
  • @WhozCraig The function can't be virtual in the base, because the class must be extremely light weight and small; any virtual functions would double its size. (In the actual code, `Tag` has a protected destructor, to avoid the obvious error.) And I'd much prefer the feature test, but using `std::is_base_of` seemed simpler for a first go. (This is the first time I've used `std::enable_if`.) – James Kanze Jul 17 '14 at 11:22
  • @JamesKanze *seriously* (re: first use of `enable_if`)? I did *not* expect that *at all*. Not that you don't browse in-general, but perusing Johanne, Xeo, and cHao's answers on SFINAE is a *very* educational experience. Worth ordering a sandwich-in for lunch and spending an hour or so. best of luck, sir. – WhozCraig Jul 17 '14 at 11:29