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;

Max
  • 4,345
  • 8
  • 38
  • 64
  • What compiler are you using? IIRC some compilers had trouble with recursion like that for a while. – Flexo Apr 04 '12 at 17:04
  • @awoodland I'm using GCC 4.6.2 :) – Max Apr 04 '12 at 17:07
  • 2
    `std::endl` isn't a function, it is a template. – Bo Persson Apr 04 '12 at 17:20
  • 1
    I'm baffled that your error messages shows your stream is a `std::string`? That doesn't match anything you said... – Mooing Duck Apr 04 '12 at 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&, , )’"` It seems to imply that `endl` is an "unresolved overloaded function type". – Robᵩ Apr 04 '12 at 17:33
  • 2
    @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>`) that has that type; that's why `ostream << endl` is valid. – Mike Seymour Apr 04 '12 at 17:55
  • 1
    @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 Apr 04 '12 at 18:12

2 Answers2

16

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));
}
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • Yes, this works perfectly, thanks. It isn't possible to typedef templated functions, right? :/ – Max Apr 04 '12 at 18:13
  • @Max: You may also consider a function. I think something like "`template basic_ostream&(*)(basic_ostream&os) get_endl(basic_ostream& s) {return std::endl;}`" should do it. – Mooing Duck Apr 04 '12 at 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 append_to_file(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 Apr 04 '12 at 18:28
  • You understand correctly, but there's no problem there! "`template basic_ostream&(*)(basic_ostream&os) get_endl(basic_string& s) {return std::endl;}`" – Mooing Duck Apr 04 '12 at 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 Apr 04 '12 at 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 Apr 04 '12 at 18:47
  • @Mooing : Probably, I just thought I'd point out the potential flaw in case he does run into it. :-] – ildjarn Apr 04 '12 at 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 Apr 04 '12 at 19:56
  • @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 Apr 04 '12 at 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 Apr 04 '12 at 20:22
  • 1
    @Max - see most recent edit for a readable version of the `get_endl` solution. – Robᵩ Apr 04 '12 at 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? :D – Max Apr 04 '12 at 22:48
  • 1
    @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 Apr 04 '12 at 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

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • It seems that std::forward(rest...) doesn't compile. GCC says: `no 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 Apr 05 '12 at 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 Apr 06 '12 at 01:18