Guillaume Chatelet
Guillaume Chatelet

Reputation: 313

Why a function pointer is not captured by universal reference?

While implementing a simple logger

struct DebugOutput {
    DebugOutput(std::ostream& out = std::cerr) : m_Out(out) {}

    template<typename T>
    inline DebugOutput& operator <<(T&& value) {
        m_Out << value;
        return *this;
    }
private:
    std::ostream& m_Out;
};

I found out std::endl wouldn't be captured by the universal reference.

DebugOutput dbg;
dgb << std::endl;

I found this this post which explains you need to add an overloaded function within the structure which takes specifically the function pointer signature, ie :

typedef std::ostream& (*StandardEndLine)(std::ostream&);
inline DebugOutput& operator<<(StandardEndLine manip) {
    return *this;
}

Why the function pointer is not captured by the universal reference ? Isn't it a type as int or void* ?

Upvotes: 12

Views: 654

Answers (1)

Cassio Neri
Cassio Neri

Reputation: 20533

A function (pointer) can be bound to a universal reference. Example:

void f(int) {}

template <typename T>
void foo(T&&) {}

foo(f); // OK

However, an overloaded function cannot. That is, if you add a second overload of f, say,

void f(double) {}

the the call foo(f) will fail.

Put yourself on the compiler shoes. It's required to pass f to foo and there are two functions named f each of which has a different type. If we inform the type, then the compiler can unambigously choose the correct f. For instance,

foo(static_cast<void (*)(int)>(f));

compiles fine and will pass void f(int) (after function-to-pointer conversion) to foo.

However, we are not informing the type. We're rather asking the compiler to deduce it.

Similarly to f, the same argument applies to std::endl because this is a function template and, therefore, the name std::endl represents a set of functions, all with the same name but different types.

Now, you can see that the cause of the error is the fact that we provide an overload set and ask for type deduction. Hence, this is not particular to universal references.

std::cout << std::endl works because basic_ostream::operator << is not a template and doesn't try to deduce the type of the passed argument. It's a function that takes one particular type of std::endl.

Upvotes: 13

Related Questions