Вывод типа шаблона для манипуляторов потока

Я не уверен, что этот код не скомпилируется.

Пример кода, с которым я работаю:

#include <iostream>
using std::cout;
using std::endl;

class Foo {
    public:
        template<typename T>
        Foo& operator<<(const T& t) {
            cout << t;
            return *this;
        }
};

int main() {
    Foo foo;
    foo << "Hello World"; // perfectly fine
    foo << endl; // shit hits the fan

    return 0;
}

Это ошибка:

test.cpp:19:12: error: no match for ‘operator<<’ in ‘foo << std::endl’
test.cpp:19:12: note: candidates are:
test.cpp:10:14: note: template<class T> Foo& Foo::operator<<(const T&)
test.cpp:10:14: note:   template argument deduction/substitution failed:
test.cpp:19:12: note:   couldn't deduce template parameter ‘T’

Я смущен тем, почему он не может заменить тип функции endl (ostream& (*)(ostream&)) на T, где явно можно делать это, когда вы указываете cout << endl;

Меня также озадачивает, что это решает проблему [отредактировано]

Foo& operator<<(ostream& (*f)(ostream&)) {
    cout << f;
    return *this;
}

В случае, если вопрос не ясен, я спрашиваю, почему он не мог вывести шаблон в первую очередь.


person Anthony Sottile    schedule 23.02.2013    source источник
comment
Чего вы пытаетесь достичь? По какой-то конкретной причине вы не исходите из std::basic_ostream<C,CT> или не реализуете streambuf?   -  person sehe    schedule 23.02.2013
comment
На последний вопрос: во-первых, он не мог вывести шаблон, потому что были применимы множественные перегрузки - следовательно, случай ссылки на функцию был неоднозначным.   -  person sehe    schedule 23.02.2013


Ответы (2)


endl — это манипулятор, т. е. это неразрешенный тип функции. Существует несколько перегрузок, и вывод типа не может решить, какую из них вы хотите.

Более конкретно, вот как выглядит endl (в GNU libc++):

/**
 *  @brief  Write a newline and flush the stream.
 *
 *  This manipulator is often mistakenly used when a simple newline is
 *  desired, leading to poor buffering performance.  See
 *  http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt11ch25s02.html
 *  for more on this subject.
*/
template<typename _CharT, typename _Traits>
  inline basic_ostream<_CharT, _Traits>&
  endl(basic_ostream<_CharT, _Traits>& __os)
  { return flush(__os.put(__os.widen('\n'))); }

Обновлено Итак, проблема в том, что компилятор не может определить, какой экземпляр endl вы должны передать (это неразрешенная перегрузка). Вы можете обойти это, выполнив вместо этого static_cast<ostream&(*)(ostream&)>(endl).

Конечно, это не удобно. Вот простое решение: http://liveworkspace.org/code/2F2VHe%241.

#include <iostream>
using std::cout;
using std::endl;

class Foo : public std::ostream
{
    public:
        template<typename T>
        Foo& operator<<(T&& t) {
            cout << std::forward<T>(t);
            return *this;
        }

        typedef std::ostream& (manip)(std::ostream&);

        Foo& operator<<(manip& m) {
            cout << m;
            return *this;
        }
};

int main() {
    Foo foo;
    foo << "Hello World"; // perfectly fine
    foo << endl; // everything is fine

    return 0;
}
person sehe    schedule 23.02.2013
comment
Не могли бы вы объяснить подробнее, причина в том, что на самом деле он не пытается определить правильную замену различных перегрузок? Или я слишком многого жду от компилятора? - person Anthony Sottile; 23.02.2013
comment
@AnthonySottile Я предоставил простой пример того, как обслуживать манипулятор endl: liveworkspace.org/code/2F2VHe$1 - person sehe; 23.02.2013
comment
Не могли бы вы приукрасить различия между вашим кодом и кодом, который у меня был выше, и причины различий. Например, мое текущее решение делает то же самое с явным определением перегрузки для манипулятора, но не могли бы вы объяснить использование std::forward. Также не могли бы вы объяснить, почему компилятор не может выбрать перегрузку без этого явного определения. - person Anthony Sottile; 23.02.2013
comment
Я приукрасил объяснение endl как манипулятора, и подробно ответил на ваш последний вопрос. - person sehe; 23.02.2013
comment
@JohannesSchaub-litb Упс. Я на самом деле не пробовал это :). Я «исправил» это, попросив указатель на функцию (было бы нормально ostream&(&)(ostream&)?) - person sehe; 23.02.2013

Проблема в том, что endl — это манипулятор, определенный как шаблон функции. В параграфе 27.7.1 стандарта C++11 указана его подпись:

template <class charT, class traits>
basic_ostream<charT,traits>& endl(basic_ostream<charT,traits>& os);
template <class charT, class traits>

Кроме того, в соответствии с пунктом 13.3.1 о разрешении перегрузки:

В каждом случае, когда кандидат является шаблоном функции, специализации шаблона функции-кандидата генерируются с использованием вывода аргументов шаблона (14.8.3, 14.8.2). Затем эти кандидаты обрабатываются как функции-кандидаты обычным способом.

Ваш operator << определен как шаблон, и компилятору необходимо вывести тип T. Однако как компилятор может узнать, какой экземпляр endl вы имели в виду? Как он может вывести аргументы шаблона charT и traits? В вашем вызове operator << нет ничего другого, из чего можно было бы сделать вывод.

У вас есть два выхода из этой проблемы. Либо вы явно указываете тип endl, чтобы сообщить компилятору, какая перегрузка должна быть выбрана:

foo << (std::ostream& (*)(std::ostream&))endl;

Или, как вы это сделали, вы создаете перегрузку operator <<, которая принимает функцию с этой конкретной сигнатурой. Теперь ваш компилятор выберет его:

Foo& operator<<(ostream& (*f)(ostream&)) 
{
    return *this << f;
}

Внутри этого определения функции нет никакой двусмысленности относительно того, что такое f: его тип точно определен. Однако будьте осторожны: эта функция вряд ли сделает то, что вы ожидаете! На самом деле, он просто продолжает вызывать сам себя, создавая бесконечную рекурсию!

Следовательно, это утверждение:

[...] обратите внимание, что я на самом деле вызываю реализацию другого метода:

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

person Andy Prowl    schedule 23.02.2013
comment
черт, хороший улов, я, вероятно, должен был запустить свой код, а не просто проверять его компиляцию... - person Anthony Sottile; 23.02.2013