7

In my code, I use variadic template functions for the logging purpose. But when I use std::endl as parameter, I get the following compiler error:

Error: no matching function for call to 'LOG_ERROR(const char [14], int&, )' LOG_ERROR("Sum of x+y = ", z, std::endl);

note: candidate: 'void LOG_ERROR()' inline void LOG_ERROR() {

note: candidate expects 0 arguments, 3 provided

My Code:

#include <iostream>

inline void LOG_ERROR() { 
    std::cout << std::endl;
}

template<typename First, typename ...Rest>
void LOG_ERROR(First && first, Rest && ...rest){
    std::cout << std::forward<First>(first);
    LOG_ERROR(std::forward<Rest>(rest)...);
}

int main() {
    int foo=40;
    LOG_ERROR("My foo = ", foo, std::endl);
}

The code works fine with "\n" but I would love to learn why it fails with std::endl and how I can fix it

eneski
  • 1,575
  • 17
  • 40
  • 2
    The issue is also discussed [here](https://stackoverflow.com/q/22927906/9593596). I don't think it's a duplicate though, as your question involves passing `std::endl` in parameter pack to be expanded?! – lubgr Aug 16 '18 at 07:10
  • 3
    `std::endl` is a function template, not just a function. You're passing it without deduction (either specific or deduced). If you populated the template to an actual function instantiation (such as `std::endl>`, it should work. – WhozCraig Aug 16 '18 at 07:14
  • @WhozCraig The thing here is I won't be the user of the `LOG_ERROR` function but I would like the user to be able to use the function with `std::endl`. – eneski Aug 16 '18 at 07:25
  • 1
    @eneski I understand, but I don't know what to tell you. The problem is the context in which it is used. There is no "there" there until such time as `std::endl` is used in a deducible context, which it isn't in your case. – WhozCraig Aug 16 '18 at 07:36

4 Answers4

8

Long story short - std::endl is function template which template arguments can't be deduced while passing. You can help Your compiler this way:

LOG_ERROR("My foo = ", foo, std::endl<char, std::char_traits<char>>);

As much as I feel this is ugly piece of code it works perfectly.

bartop
  • 9,971
  • 1
  • 23
  • 54
  • `auto endl = std::endl>;` helps. – YSC Aug 16 '18 at 13:11
  • @YSC If I were to choose, I'd go with suggested somwhere down here: `auto endl = [](auto&&os) -> decltype(auto) { return os << std::endl; }` – bartop Aug 16 '18 at 13:13
  • Don't do this. It doesn't "work perfectly" - it's unspecified. The standard library reserves the right to break this code at any time - you should not take the address of or explicitly specify standard library functions/function templates. You have to use a lambda (or normal function object). – Barry Aug 16 '18 at 13:25
  • @Barry Good to know. Thank you. – YSC Aug 16 '18 at 13:36
  • @Barry Could You reference to standard? I never heard of such a restriction – bartop Aug 16 '18 at 18:55
  • @bartop [\[namespace.std\]](http://eel.is/c++draft/namespace.std). I guess technically since `endl` is an addressable, non-member function template, it's safe in this case as covered by [\[global.functions\]](http://eel.is/c++draft/global.functions) but it's still nicer to just avoid it entirely. – Barry Aug 16 '18 at 19:29
7

Until someone comes with a better solution, you can use a trivial wrapper with an appropriate operator overload:

struct EndlWrap {};

std::ostream& operator << (std::ostream& os, EndlWrap) {
   return os << std::endl;
}

which should be usable like this:

LOG_ERROR("My foo = ", foo, EndlWrap{});

This has an advantage when your logging destination might be a non-standard stream, i.e., the template arguments of std::endl can still be deduced when it's <<'d into the stream.

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • 4
    Possibly: `auto EndlWrap = [](auto&&os) -> decltype(auto) { return os << std::endl; }`. it works also with `std::wcout`. (or templatize your function). – Jarod42 Aug 16 '18 at 07:30
  • That's nice!! The question is tagged C++11, though. – lubgr Aug 16 '18 at 07:32
  • So fallback to old functor class. – Jarod42 Aug 16 '18 at 07:34
  • This would work, too: `auto EndlWrap = [](std::ostream& os) -> std::ostream& { return os << std::endl; };` – lubgr Aug 16 '18 at 07:36
  • Right... functor class with a `<<` template seems to be the way to go then. – lubgr Aug 16 '18 at 07:44
  • I meant `struct { template OS& operator()(OS& os) const { return os << std::endl; } } EndlWrapper;` (the "equivalent" of the lambda). – Jarod42 Aug 16 '18 at 07:54
  • Hm, but that doesn't compile. The stateless lamdba converts to a function pointer, for which `<<` is overloaded, but that doesn't apply for the struct. – lubgr Aug 16 '18 at 08:08
  • I so miss the `template operator OS& (*)(OS&)`... (and so replace const method by static one). – Jarod42 Aug 16 '18 at 09:20
1

std::endl is not a character type or any other type. It is output stream manipulator. Its return type is output stream.

So, you can not pass it without typecasting. Please look here

eneski
  • 1,575
  • 17
  • 40
user2641018
  • 108
  • 5
1

You can use defaulted template parameters and defaulted function arguments instead of a variadic template.

The code is less clean and you will have to choose a limitation on the number of parameters, but it will do the job:

template<class...>
inline void LOG_ERROR_();
template<>
inline void LOG_ERROR_<>() { 
    std::cout << std::endl;
}
template<typename First, typename ... Rest>
void LOG_ERROR_(First && first, Rest && ...rest){
    std::cout << std::forward<First>(first);
    LOG_ERROR_<Rest...>(std::forward<Rest>(rest)...);
    //could be cleaner with if constexpr
}

using manip_t = std::basic_ostream<char>&(*)(std::basic_ostream<char>&);

std::basic_ostream<char>& no_manip(std::basic_ostream<char>& o){
    return o;
}

template<typename A=manip_t
  ,typename B=manip_t, typename C= manip_t
  ,typename D=manip_t // to be continued
  >
inline void LOG_ERROR(A&& a=no_manip, B&& b=no_manip,C&& c=no_manip
              ,D&& d=no_manip /*to be continued*/){
    LOG_ERROR_<A,B,C,D/*to be continued*/>(
       std::forward<A>(a),std::forward<B>(b),std::forward<C>(c),
        std::forward<D>(d)/*to be continued*/);
}

Depending on the compiler this code could produce ugly assembly. One solution is to write an overload for each possible number of argument, or have a good knowldge of compiler specific function attributes (always_inline,etc...)

Oliv
  • 17,610
  • 1
  • 29
  • 72