Search code examples
c++templatesstlvariadic-templates

C++ function strprint(expr, expr, expr...) almost works, but not quite, why?


Here's a small C++ 'strprint' function that seems to mostly work:

#include <sstream>
#include <iostream>

// send_to_stream: send all arguments to the specified stream.
inline void send_to_stream(std::ostream &os) {}
template <typename ARG, typename... REST>
inline void send_to_stream(std::ostream &os, const ARG &arg, const REST & ... rest) {
    os << arg;
    send_to_stream(os, rest...);
}

// strprint: convert all arguments to a string by sending them to a stringstream.
template <typename... ARGS>
std::string strprint(const ARGS & ... args) {
    std::ostringstream oss;
    send_to_stream(oss, args...);
    return oss.str();
}

int main(int argc, char **argv) {
    std::string s1 = strprint("five:", 5, "\n");
    std::cout << "S1=" << s1 << "." << std::endl;

    // std::string s2 = strprint("five:", 5, std::endl);
    // std::cout << "S2=" << s1 << "." << std::endl;
};

This program, when run, produces the output:

five:5
.

which is exactly what I would expect. But when I uncomment the 's2' lines, I get a compile error:

foo.cpp: In function ‘int main(int, char**)’:
foo.cpp:25:53: error: too many arguments to function ‘std::string strprint(const ARGS& ...) [with ARGS = {}; std::string = std::__cxx11::basic_string<char>]’
   25 |      std::string s2 = strprint("five:", 5, std::endl);
      |                                                     ^
foo.cpp:15:13: note: declared here
   15 | std::string strprint(const ARGS & ... args) {
      |             ^~~~~~~~

This makes no sense. Too many arguments to a function that takes an unlimited number of arguments? It doesn't seem to like passing 'std::endl' through a variadic list. Why not? What am I missing?


Solution

  • std::endl is a function template:

    template< class CharT, class Traits >
    std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );
    

    Admittedly, the error message is rather confusing. The problem is that the type of std::endl cannot be deduced, because you would need to instantitate the function first:

    std::string s2 = strprint("five:", 5, std::endl<std::ostream::char_type,std::ostream::traits_type>);
    std::cout << "S2=" << s1 << "." << std::endl;
    

    Live Demo

    The usual

    std::cout << std::endl;
    

    works due to overload 20 here https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt. Generally all io manipulators are functions that take the stream as parameter. And generall all io manipulators that are only defined for input or output streams, respectively, are function templates.

    Of course you do not want to explicitly provide the char_type and traits_type. As pointed out by Remy Lebeau, you can use a (non-capturing) lambda:

    auto endl = [](std::ostream os&){ os << std::endl; }; 
    std::string s2 = strprint("five:", 5, endl);
    

    This will call overload 18 (from avove link).