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

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

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

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

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

4

Решение

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$1

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

Другие решения

Проблема в том, что 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 это: его тип точно определен. Однако, будьте осторожны: эта функция, скорее всего, не выполняет то, что вы ожидаете! На самом деле, он просто продолжает называть себя, генерируя бесконечная рекурсия!

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

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

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

2

По вопросам рекламы [email protected]