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.
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).
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>>);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With