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;

7
  • What compiler are you using? IIRC some compilers had trouble with recursion like that for a while. Commented Apr 4, 2012 at 17:04
  • 2
    std::endl isn't a function, it is a template. Commented Apr 4, 2012 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... Commented Apr 4, 2012 at 17:25
  • 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<char, char_traits<char>>) that has that type; that's why ostream << endl is valid. Commented Apr 4, 2012 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. Commented Apr 4, 2012 at 18:12

2 Answers 2

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)); } 
Sign up to request clarification or add additional context in comments.

13 Comments

Yes, this works perfectly, thanks. It isn't possible to typedef templated functions, right? :/
@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.
@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 - see most recent edit for a readable version of the get_endl solution.
@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"
|
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

2 Comments

It seems that std::forward<Args...>(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 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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.