Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there some trick that would allow me to pass stream manipulators to a variadic template function?

I've been trying to write a thread-safe wrapper for std::cout and figured it's a good time to learn me some variadic templates.

Like that.

Then, just when I thought I got it right, I noticed it doesn't work with std::endl.

Take this code:

template <typename... P>
void f(P...){}

int main()
{
    f(1,2,3,std::endl);
}

When you try to compile it, GCC complains in a very silly way:

main.cpp:18:19: error: too many arguments to function 'void f(P ...) [with P = {}]'

When you try that with a regular template, you get

main.cpp:22:13: error: no matching function for call to 'f(<unresolved overloaded function type>)'

which actually makes sense.

It's not a big issue for me, I can do it in some other way, but I'd really like to know if there's a way to go around that limitation.

like image 206
user697683 Avatar asked Jun 15 '13 00:06

user697683


3 Answers

Instead of the explicit template arguments as suggested by Andy Prowl, I recommend template argument deduction:

C:\Temp>type meow.cpp
#include <iostream>
#include <utility>
using namespace std;

void Print() { }

template <typename T, typename... Rest> void Print(T&& t, Rest&&... rest) {
    cout << forward<T>(t);
    Print(forward<Rest>(rest)...);
}

int main() {
    ostream& (* const narrow_endl)(ostream&) = endl;

    Print("Hello, world!", narrow_endl, "I have ", 1729, " cute fluffy kittens.",
        static_cast<ostream& (*)(ostream&)>(endl)
    );
}

C:\Temp>cl /EHsc /nologo /W4 /MTd meow.cpp
meow.cpp

C:\Temp>meow
Hello, world!
I have 1729 cute fluffy kittens.

N3690 13.4 [over.over] specifies the rules being used here, which go back to C++98. Basically, taking the address of an overloaded and/or templated function is ambiguous in general, but permitted in specific contexts. Initialization and static_casting are two of those contexts, because they provide sufficient type information to disambiguate. This allows template argument deduction to proceed normally.

Explicit template arguments are very tempting, but they can explode in all sorts of ways. It's unlikely that std::endl will ever be changed in a way that breaks explicit template arguments here, but I really recommend against them (except when things are specifically designed for them, like forward and make_shared).

like image 113
Stephan T. Lavavej Avatar answered Sep 30 '22 06:09

Stephan T. Lavavej


The problem is that manipulators like std::endl are function templates. Therefore, you have to be explicit about which specialization of that function template you want to be passed (otherwise, no type deduction is possible).

For instance:

f(1, 2, 3, &std::endl<char, std::char_traits<char>>);
//                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
like image 42
Andy Prowl Avatar answered Sep 30 '22 08:09

Andy Prowl


template functions are not functions, and std::endl is a template function.

You cannot pass a template function around. You can, however, pass a function object which represents an overload set around. Writing such a functor is pretty easy:

struct endl_overloadset {
  template<typename... Args>
  auto operator()(Args&&...args)const
    ->decltype(std::endl( std::forward<Args>(args) ) )
    { return ( std::endl( std::forward<Args>(args) ) ) };

  template<typename T,typename=typename std::enable_if<\
    std::is_same< decltype(static_cast<T>( std::endl )), T >::value\
  >::type>\
  operator T() const { return std::endl; }
};

but I find that to be a bit too much like boilerplate, so write up some macros that do the job for you:

#define RETURNS(X) ->decltype(X) { return (X); } // C++11 lacks non-lambda return value deduction
#define OVERLOAD_SET(F) struct {\
  template<typename... Args>\
  auto operator()(Args&&...args)const\
    RETURNS( F( std::forward<Args>(args)... ) )\
  template<typename T,typename=typename std::enable_if<\
    std::is_same< decltype(static_cast<T>( F )), T >::value\
  >::type>\
  operator T() const { return F; }\
}
static OVERLOAD_SET(std::endl) Endl;

then pass Endl to your f, and calling Endl(Blah) ends up doing std::endl(Blah). Similarly, assigning Endl to a variable, or passing it to a method, does basically the same as assigning std::endl to a variable or passing it to a method (wrt overload resolution).

Sadly, the OVERLOAD_SET cannot be used within a function, as local types cannot have template methods. If it could be used within a function, then:

f(1,2,3, OVERLOAD_SET(std::endl)() );

would do what you wanted. But that would be the language you want to program in, not the language you have. (Even better would be @Xeo's proposal to allow overload set functors to be automatically generated using some random further abuse of the [] syntax, rather than relying on macros).

Live example, where I pass my endl_functor to a print method and then use << on it without any further ado.

like image 23
Yakk - Adam Nevraumont Avatar answered Sep 30 '22 06:09

Yakk - Adam Nevraumont