Сбой разрешения перегрузки при потоковом объекте через неявное преобразование в строку

Отказ от ответственности: Я знаю, что следует избегать неявного преобразования в строку и что правильный подход будет перегрузкой op<< для Person.


Рассмотрим следующий код:

#include <string>
#include <ostream>
#include <iostream>

struct NameType {
   operator std::string() { return "wobble"; }
};

struct Person {
   NameType name;
};

int main() {
   std::cout << std::string("bobble");
   std::cout << "wibble";

   Person p;
   std::cout << p.name;
}

Это дает в GCC 4.3.4 следующее:

prog.cpp: In function ‘int main()’:
prog.cpp:18: error: no match for ‘operator<<’ in ‘std::cout << p.Person::name’
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:112: note: candidates are: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>& (*)(std::basic_ostream<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:121: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ios<_CharT, _Traits>& (*)(std::basic_ios<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:131: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::ios_base& (*)(std::ios_base&)) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:169: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:173: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:177: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(bool) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/bits/ostream.tcc:97: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:184: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/bits/ostream.tcc:111: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:195: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:204: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long long int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:208: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long long unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:213: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(double) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:217: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(float) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:225: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long double) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:229: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(const void*) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/bits/ostream.tcc:125: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_streambuf<_CharT, _Traits>*) [with _CharT = char, _Traits = std::char_traits<char>]

Почему бесплатный op<<(ostream&, string const&) не попадает в набор перегрузки? Это связано с тем, что комбинация желаемой перегрузки является экземпляром шаблона и... ADL?

Ответ 1

14.8.1/4 в С++ 98

Неявные преобразования (раздел 4) будут выполняться на аргументе функции, чтобы преобразовать его в тип соответствующего параметра функции, если тип параметра не содержит шаблонных параметров, которые участвуют в выводе аргумента шаблона.

Здесь вы хотели бы создать экземпляр

template <class charT, class traits, class Allocator>
  basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>&,
               const basic_string<charT, traits, Allocator>&);

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

Ответ 2

Это потому, что это шаблон.

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


Неважно, если вы использовали конкретный оператор раньше в программе или нет. Каждое использование рассматривается отдельно

Перегрузками, рассматриваемыми как кандидаты, являются те, где все параметры шаблона могут быть выведены из std:: ostream или те, которые являются членами этого класса.


Что делать, если мы добавим оператор без шаблона?

#include <string> 
#include <ostream> 
#include <iostream>  

struct NameType {
   operator std::string() { return "wobble"; } 
};  

struct Person {
    NameType name;
};  

void operator<<(std::ostream& os, const std::string& s)   // ** added **
{ std::operator<<(os, s); }

int main() 
{    
    std::cout << std::string("bobble");
    std::cout << "wibble";

     Person p;
     std::cout << p.name; 
}  

Теперь он работает и выводит

 bobblewibblewobble

Ответ 3

Это потому, что пользовательская функция преобразования не учитывается в ADL. ADL означает, что набор перегрузки содержит функцию (и) перегрузки из пространства имен, в котором определяется аргумент. Здесь тип аргумента operator<< равен NameType, но operator << (std::ostream&, const NameType&) не был определен в пространстве имен, в котором определяется NameType. Следовательно, ошибка, так как поиск соответствующей перегрузки прекращается прямо там. Это то, что ADL. ADL не идет дальше, чтобы изучить определение NameType, чтобы определить, определяет ли он какую-либо пользовательскую функцию преобразования или нет.

Вы получите ту же ошибку, если вы выполните следующее:

NameType name;
std::cout << name ; //error: user-defined conversion not considered.

Вам нужно cast it:

std::cout << (std::string)name << std::endl; //ok - use std::string()

Кроме того, у вас может быть несколько пользовательских функций преобразования:

std::cout << (int)name << std::endl; //ok - use int() instead

Вывод на ideone:

wobble
100

Ответ 4

Преобразование в строку вызывается только в некоторых случаях:

a) запрошено явно (string) p.name

b) присвоение строки string a = p.name

c)...

Если данный случай не подходит, вы можете принудительно вызвать invokation ostream<<(ostream&,string) по крайней мере двумя способами:

  • http://ideone.com/SJe5W Создание NameType - это строка (по общему наследованию).

  • перейти к случаю a): явно указать преобразование, как показано в примере с преобразованием в (int).

Мне действительно нравится вариант 1.

Ответ 5

Это потому, что определенные пользователем конверсии не могут быть скованы. Объяснить с помощью примера:

struct A {
  void operator = (const int i);
};

struct B {
  operator int ();
}

A a;
B b;
a = b;  // error! because, compiler will not match "A::operator=" and "B::operator int"

Вот похожий вопрос, я спросил когда-нибудь.

В вашем случае ваши первые пользовательские конверсии,

(1) NameType::operator std::string()

(2) operator <<(ostream&, const std::string&), что несколько напоминает ostream::operator<<(std::string&).

Когда вы пишете, cout << p.name; Теперь два типа объектов встречаются лицом к лицу:

ostream (LHS) <====> NameType (RHS)

Теперь operator <<(ostream&, const string&) вызывается только, если RHS string. Но вот это NameType; поэтому он не вызывается.

И, NameType::operator string () вызывается только, если LHS string. Но вот это ostream; поэтому он не вызывается.

Сделать это уравнение истинным; bot из вышеперечисленных методов оператора должен быть вызван компилятором. Но это не поддерживается С++. Почему он не поддерживается, описан в ссылке, опубликованной выше.