So, I was trying to write a function like this:
void append_to_stream(std::ostream &stream)
{ }
template <typename T, typename... Args>
void append_to_stream(std::ostream &stream, T first, Args&&... rest)
{
stream << first;
append_to_stream(stream, rest...);
}
and call it like:
append_to_stream(stream,
std::endl,
std::endl);
But this doesn't work. I get an error that says 'too many arguments' to the function. I've narrowed it down to the point I know that the std::endl
is guilty - probably because it's a function. I managed to 'solve' this by declaring a struct called endl
and define the <<operator
for it so that it simply calls std::endl
. This works but doesn't feel particularly good. Is it not possible to accept std::endl as a template argument? The function works for other types.
Edit: here's the error:
src/log/sinks/file_sink.cpp:62:21: error: too many arguments to function ‘void log::sinks::append_to_stream(std::string&, Args&& ...) [with Args = {}, std::string = std::basic_string<char>]’
Update
Trying to get the compiler to deduce the correct template arguments @MooingDuck suggested that a function of the following form could be used:
template<class e, class t, class a>
basic_ostream<e,t>&(*)(basic_ostream<e,t>&os) get_endl(basic_string<e,t,a>& s)
{
return std::endl<e,t>;
}
However, this doesn't compile.
Error:
src/log/sinks/file_sink.cpp:42:28: error: expected unqualified-id before ‘)’ token
src/log/sinks/file_sink.cpp:42:53: error: expected initializer before ‘get_endl’
Any ideas why? For the sake of compiling this, I've added using namespace std;
std::string
? That doesn't match anything you said.. - Mooing Duck 2012-04-04 17:25
endl
is a template, not a function. Why, then does g++ report this error? "t.cc:23:27: error: no matching function for call to ‘append_to_stream(std::ostream&, <unresolved overloaded function type>, <unresolved overloaded function type>)’"
It seems to imply that endl
is an "unresolved overloaded function type" - Robᵩ 2012-04-04 17:33
endl
, which form a set of overloaded functions. Since the function argument type is generic, then any of these would match, and the compiler can't choose from them. If the argument had a specific type (like ostream&(*)(ostream&)
), then the compiler could pick the specialisation (endl<char, char_traits<char>>
) that has that type; that's why ostream << endl
is valid - Mike Seymour 2012-04-04 17:55
std::string
argument in. The real function is wrapped by a function that creates an fstream
- Max 2012-04-04 18:12
std::endl
is a template, not a function, and the compiler cannot resolve which endl
to use.
Try:
append_to_stream(std::cout,
std::endl<char, std::char_traits<char>>,
std::endl<char, std::char_traits<char>>);
Or, MooingDuck's solution (corrected):
template<class e, class t, class a> //string version
std::basic_ostream<e, t>& (*get_endl(const std::basic_string<e, t, a>&))
(std::basic_ostream<e, t>& )
{ return std::endl<e,t>; }
template<class e, class t> //stream version
std::basic_ostream<e, t>& (*get_endl(const std::basic_ostream<e, t>&))
(std::basic_ostream<e, t>& )
{ return std::endl<e,t>; }
int main () {
std::ostream& stream = std::cout;
append_to_stream(stream,
get_endl(stream),
get_endl(stream));
}
Here is get_endl solution, simplified by C++11 decltype
feature:
template<class e, class t, class a> //string version
auto get_endl(const std::basic_string<e, t, a>&)
-> decltype(&std::endl<e,t>)
{ return std::endl<e,t>; }
template<class e, class t> //stream version
auto get_endl(const std::basic_ostream<e,t>&)
-> decltype(&std::endl<e,t>)
{ return std::endl<e,t>; }
int main () {
std::ostream& stream = std::cout;
append_to_stream(stream,
get_endl(stream),
get_endl(stream));
}
template<class e, class t> basic_ostream<e,t>&(*)(basic_ostream<e,t>&os) get_endl(basic_ostream<e,t>& s) {return std::endl<e,t>;}
" should do it - Mooing Duck 2012-04-04 18:19
template<class e, class t, class a> basic_ostream<e,t>&(*)(basic_ostream<e,t>&os) get_endl(basic_string<e,t,a>& s) {return std::endl<e,t>;}
- Mooing Duck 2012-04-04 18:30
stringstream
- Mooing Duck 2012-04-04 18:47
std::endl
object that matches it's character traits. The syntax for functions that return functions is crazy confusing, which is why I got it wrong. : - Mooing Duck 2012-04-04 20:22
auto
means the return type is on the right side instead of the left (so that we can use decltype
on a parameter). It's apparently called a "trailing-return-type - Mooing Duck 2012-04-04 22:57
Much easier than specifying template arguments or defining a whole new template(!) is resolving the overload by cast.
typedef std::ostream & (&omanip_t)( std::ostream & );
append_to_stream(stream,
static_cast< omanip_t >( std::endl ),
static_cast< omanip_t >( std::endl ) );
This will work for all manipulators, whereas some manipulators could be templated differently, for example if user-provided.
Also, you should pass T first
by either perfect forwarding or const reference. It doesn't make much sense to forward first, and then pass by value. Also, without a call to std::forward
, rvalue arguments will be passed by value… just following the idiom, it would be
template <typename T, typename... Args>
void append_to_stream(std::ostream &stream, T &&first, Args&&... rest)
{
stream << std::forward< T >( first );
append_to_stream(stream, std::forward< Args >( rest ) ... );
}
...
, but I can't tell offhand why this way doesn't work as well. See update. The difference is that const &
specifies that you won't modify the argument, so it's probably better in this case anyway. Note that forward
doesn't depend on rvalue references and will pass by ordinary modifiable lvalue ref if you give it a plain local variable name - Potatoswatter 2012-04-06 01:18