Сбой разрешения перегрузки при потоковом объекте через неявное преобразование в строку
Отказ от ответственности: Я знаю, что следует избегать неявного преобразования в строку и что правильный подход будет перегрузкой 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 из вышеперечисленных методов оператора должен быть вызван компилятором. Но это не поддерживается С++. Почему он не поддерживается, описан в ссылке, опубликованной выше.