Почему указатель функции не захватывается универсальной ссылкой?

При использовании простого регистратора

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;
};

Я обнаружил, что std::endl не будет захвачен универсальной ссылкой .

DebugOutput dbg;
dgb << std::endl;

Я нашел этот этот пост, в котором объясняется, что вам нужно добавить перегруженную функцию внутри структуры, которая специально использует подпись указателя функции, то есть:

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

Почему указатель функции не захватывается универсальной ссылкой? Разве это не тип int или void*?

Ответ 1

Функция (указатель) может быть привязана к универсальной ссылке. Пример:

void f(int) {}

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

foo(f); // OK

Однако перегруженная функция не может. То есть, если вы добавите вторую перегрузку f, скажем,

void f(double) {}

вызов foo(f) завершится с ошибкой.

Поставьте себя на обувь компилятора. Он должен пройти f до foo, и есть две функции с именем f, каждая из которых имеет другой тип. Если мы сообщим тип, то компилятор может однозначно выбрать правильный f. Например,

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

компилируется и передает void f(int) (после преобразования функции в указатель) в foo.

Однако мы не сообщаем тип. Мы скорее просим компилятор вывести его.

Аналогично f, тот же аргумент применяется к std::endl, потому что это шаблон функции, и поэтому имя std::endl представляет собой набор функций с одинаковым именем, но с разными типами.

Теперь вы можете видеть, что причиной ошибки является тот факт, что мы предоставляем набор перегрузки и запрашиваем вывод типа. Следовательно, это не относится к универсальным ссылкам.

std::cout << std::endl работает, потому что basic_ostream::operator << не является шаблоном и не пытается вывести тип переданного аргумента. Это функция, которая принимает один конкретный тип std::endl.