2
votes

I'm porting a piece of code which was written for Linux and compiled with gcc v4.8.2 (using -std=c++11) to Windows. Part of it is an overloaded operator<< which MSVC++ 2013 doesn't agree with. A condensed example is below.

We've defined a custom stream class which supports the standard stream manipulator functions. For this reason we've overloaded operator<< which accepts a function pointer as second argument.

If I define a templated overload of operator<< for . MSVC++ complains:

error C2676: binary '<<' : 'foobar' does not define this operator or a conversion to a type acceptable to the predefined operator

But if I specify the type of the second argument as std::ostream instead of the templated std::basis_ostream<..,..> the code works as expected.

At first I thought I had messed up the template arguments. However, if I define an arbitrary template function instead of an overload of operator<< the code compiles fine. What's going on here?

Example

#include "stdafx.h"

#include <iostream>
#include <sstream>
#include <string>

struct foobar {
    std::stringstream s_;
};

/* Causes compiler error C2676
template <typename CharT = char, typename Traits = std::char_traits<CharT> >
foobar& operator<<(foobar& foo, std::basic_ostream<CharT, Traits>& (*manip)(std::basic_ostream<CharT, Traits>&))
{
    foo.s_ << manip;
    return foo;
}
 */

/* works as intendend */
foobar& operator<<(foobar& foo, std::ostream& (*manip)(std::ostream&))
{
    foo.s_ << manip;
    return foo;
}

/* works too */
template <typename CharT = char, typename Traits = std::char_traits<CharT> >
foobar& qux(foobar& foo, std::basic_ostream<CharT, Traits>& (*manip)(std::basic_ostream<CharT, Traits>&))
{
    foo.s_ << manip;
    return foo;
}

int _tmain(int argc, _TCHAR* argv[])
{
    foobar foo;
    foo << std::endl;

    qux(foo, std::endl);
    return 0;
}
1
Looks like a compiler bug. Both g++5.1 and clang++3.6 accept the program, as well as vc++19 (see e.g. webcompiler.cloudapp.net )dyp
std::endl isn't a function, it is a template function. You pass it to a particular signature, and it finds the overload. You pass it a function with a template of types, and it screws up? But only when you are doing operator overloading.Yakk - Adam Nevraumont
Note that CharT and Traits can never be deduced (because std::endl is a function template), hence you could as well just use your second version of operator<<.dyp
Really, << of a manip should deduce its CharT and Traits from stringstream no?Yakk - Adam Nevraumont
@Yakk How? With decltype(foobar::s_)?dyp

1 Answers

1
votes

There appears to be some kind of bug with default arguments, overloaded operators, and template function parameter overload resolution.

These are all complex, so sort of understandable.

The good news is that you shouldn't be taking just any iomanip there -- you should be taking a specific one.

You can either hard code it, or deduce it like so:

// Get the nth type from a template instance:
template<class...>struct types{using type=types;};
template<class T, size_t N>
struct nth{};
template<class T, size_t N>
using nth_t=typename nth<T,N>::type;
template<template<class...>class Z, class T0, class...Ts, size_t N>
struct nth<Z<T0,Ts...>,N>:nth<types<Ts...>,N-1>{};
template<template<class...>class Z, class T0, class...Ts>
struct nth<Z<T0,Ts...>,0>{using type=T0;};

// From a String type, produce a compatible basic_stream type:
template<class String>
using compat_stream = std::basic_ostream<nth_t<String,0>, nth_t<String,1>>;

// From a type T, produce a signature of a function pointer that
// pass-through manipulates it:
template<class T>
using manip_f = T&(*)(T&);

// From a String type, produce a compatible io-manipulator:
template<class String>
using io_manip_f = manip_f< compat_stream<String> >;

// The return of foobar:
struct foobar {
  std::stringstream s_;
  // the type of manipulators that is compatible with s_:
  using manip_f = io_manip_f<std::stringstream> manip;
  // a Koenig operator<<:
  friend foobar& operator<<( foobar& self, manip_f manip ) {
    self.s_ << manip;
    return self;
  }
};