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

4
  • 2
    The issue is also discussed here. I don't think it's a duplicate though, as your question involves passing std::endl in parameter pack to be expanded?! Commented Aug 16, 2018 at 7: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<char, std::char_traits<char>>, it should work. Commented Aug 16, 2018 at 7: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. Commented Aug 16, 2018 at 7: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. Commented Aug 16, 2018 at 7:36

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

5 Comments

auto endl = std::endl<char, std::char_traits<char>>; helps.
@YSC If I were to choose, I'd go with suggested somwhere down here: auto endl = [](auto&&os) -> decltype(auto) { return os << std::endl; }
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 Could You reference to standard? I never heard of such a restriction
@bartop [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] but it's still nicer to just avoid it entirely.
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.

8 Comments

Possibly: auto EndlWrap = [](auto&&os) -> decltype(auto) { return os << std::endl; }. it works also with std::wcout. (or templatize your function).
That's nice!! The question is tagged C++11, though.
So fallback to old functor class.
This would work, too: auto EndlWrap = [](std::ostream& os) -> std::ostream& { return os << std::endl; };
Right... functor class with a << template seems to be the way to go then.
|
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

Comments

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...)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.