Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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;

like image 435
Max Avatar asked Apr 04 '12 17:04

Max


People also ask

Which parameter is allowed for non-type template?

Which parameter is legal for non-type template? Explanation: The following are legal for non-type template parameters:integral or enumeration type, Pointer to object or pointer to function, Reference to object or reference to function, Pointer to member.

How do I restrict a template type in C++?

There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.

Can we use non-type parameters as argument templates?

A non-type template argument provided within a template argument list is an expression whose value can be determined at compile time. Such arguments must be constant expressions, addresses of functions or objects with external linkage, or addresses of static class members.

What can the template parameter in C++ template definition be?

A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.


2 Answers

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));
}
like image 148
Robᵩ Avatar answered Oct 06 '22 07:10

Robᵩ


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

like image 25
Potatoswatter Avatar answered Oct 06 '22 09:10

Potatoswatter