Cannot have typeof(std::endl) as template parameter?

Go To StackoverFlow.com

10

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;

2012-04-04 17:00
by Max
What compiler are you using? IIRC some compilers had trouble with recursion like that for a while - Flexo 2012-04-04 17:04
@awoodland I'm using GCC 4.6.2 : - Max 2012-04-04 17:07
std::endl isn't a function, it is a template - Bo Persson 2012-04-04 17:20
I'm baffled that your error messages shows your stream is a std::string? That doesn't match anything you said.. - Mooing Duck 2012-04-04 17:25
@BoPersson - I accept that 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
@Robᵩ: The compiler is referring to the set of possible specialisations of 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
@MooingDuck It's a case of leaky abstraction. I tried to strip the example down to the essentials - I mistakenly left the std::string argument in. The real function is wrapped by a function that creates an fstream - Max 2012-04-04 18:12


15

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));
}
2012-04-04 17:21
by Robᵩ
Yes, this works perfectly, thanks. It isn't possible to typedef templated functions, right? : - Max 2012-04-04 18:13
@Max: You may also consider a function. I think something like "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
@MooingDuck That's a good idea, I guess you use the stream to deduce the template arguments for std::endl? The problem is that the function I want to use is appendtofile(std::string, ...) which means that at the point of instantiation I don't have the stream object handy :/. Please tell me if I've misunderstood you though - Max 2012-04-04 18:28
You understand correctly, but there's no problem there! "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
@Mooing : The one problem is that it's possible to insert narrow strings into wide streams, so that logic may not always be correct - ildjarn 2012-04-04 18:46
@ildjarn: In this case, my psychic debugging tells me that his strings are very closely died to the stream in question, possibly via stringstream - Mooing Duck 2012-04-04 18:47
@Mooing : Probably, I just thought I'd point out the potential flaw in case he does run into it. :- - ildjarn 2012-04-04 18:49
@MooingDuck I've tried, and that seems like really neat trickery, but it doesn't compile. Could you please take a look at my update in the question? : - Max 2012-04-04 19:56
@Max - See my edit - Robᵩ 2012-04-04 20:15
@Robᵩ Thanks, that works great. :) Sorry I can't give you another upvote, or accepted answer... I'm feeling a bit confused, the syntax is weirdish, what does that actually declare and define - Max 2012-04-04 20:20
@max it defines a function that takes a stream (or string, depending on which version) parameter, and returns a 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
@Max - see most recent edit for a readable version of the get_endl solution - Robᵩ 2012-04-04 20:38
@MooingDuck & Rob, thanks :) I'll have to read up a bit on that syntax. Do you know what that is called so I can search for it? decltype is used for other things, and it seems you are declaring the return type as auto? : - Max 2012-04-04 22:48
@Max: 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


4

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 ) ... );
}

http://ideone.com/cw6Mc

2012-04-05 01:29
by Potatoswatter
It seems that std::forwardno matching call to std::forward(). I interpret this to mean that the base-case doesn't work, where rest... is empty. This is supported by the fact that if I change the base-case to accept the last parameter, it compiles fine. That said, is it preferably to pass using rvalue and std::forward or ordinary const ref? What is the difference? : - Max 2012-04-05 18:06
@Max Yikes, I got the forwarding idiom wrong! Both packs should be expanded by a single ..., 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
Ads