10

В следующем фрагменте кода (live on coliru):неявный приоритет оператора преобразования

#include <iostream> 
#include <string> 

int main() 
{ 
    struct S { 
     operator bool  () const { return false; } 
     operator std::string() const { return "false"; } 
    } s; 
    std::cout << s << "\n"; // outputs 0 
} 

Как компилятор выбрать выбрать неявное преобразование в bool над std::string?

Моя гипотеза заключается в том, что в этом случае это может быть чисто порядок декларирования различных вкусов std::basic_ostream::operator<<, но все ли? Стандарт ли что-то говорит о выборе конкретного неявного преобразования?

+2

Просто придираться, никогда ничего не неявно.Типы неявно преобразуются, но * cast * означает * явное преобразование *. – StoryTeller

+0

@StoryTeller Спасибо, хорошо знать конкретную терминологию. Я отредактировал свой вопрос. – YSC

ответ

10

Напомним, что std::string не является самостоятельным типом, это действительно специализация шаблона класса - std::basic_string<char>. Очень важной деталью является то, что потенциал перегрузки для потоковой std::string не принимает std::string const& аргумент, это function template, что выводит std::basic_string const&: Шаблон вычет

template <class CharT, class Traits, class Allocator> 
std::basic_ostream<CharT, Traits>& 
    operator<<(std::basic_ostream<CharT, Traits>& os, 
       const std::basic_string<CharT, Traits, Allocator>& str); 

никогда не считает преобразования. Поиск имени найдет этот шаблон функции, а затем отменит его как нежизнеспособный из-за отказа дедукции. S не является basic_string<CharT, Traits, Allocator> для любых таких типов, поэтому мы закончили. Единственными жизнеспособными операторами потока будут все интегральные, из которых bool - лучший матч.

Если специально была функция с подписью:

std::ostream& operator<<(std::ostream&, std::string const&);  

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


Это легко проверить с помощью собственных функций, а не миллион перегрузок для operator<<:

void foo(bool); // #1 
void foo(std::string); // #2 

void bar(bool); // #3 
template <class C, class T, class A> 
void bar(std::basic_string<C,T,A>); // #4 

foo(S{}); // error: ambiguous 
bar(S{}); // calls #3 
+0

@YSC: Я отредактировал мой, чтобы исправить это. Я не осознавал, что в то время это была несовершенная версия Барри. Я все еще оставил это, потому что я думаю, что «не делай этого, тогда» совет полезен. –

+0

@Barry, вывод для оператора шаблона <<. Компилятор выбирает функцию-член, потому что это не шаблон. См. Мой ответ. – Oliv

+0

Яркий способ увидеть это для OP - это комментарий 'operator bool' от исходного кода ... тогда он не скомпилируется (он не вызывает строковый преобразователь) –

2
ostream& operator<<(bool value); 

Является функцией-членом от std::ostream. С другой стороны:

std::ostream& operator<<(std::ostream& os, const std::string& str); 

является автономной функцией - , который фактически объявлен в качестве шаблона. Ссылка на S не соответствует ни одному из шаблонов - поэтому он не рассматривается для расширения шаблона.


Можно однозначно определить, какая перегрузка должна быть выбрана, но я предлагаю вам этого не делать.

а) Это всегда одна из сложных углов стандарта (так что вы можете столкнуться с ошибками компилятора;

б) будущие разработчики всегда найдут код трудно читать.

Мое предложение состоит в том, чтобы избежать проблемы, просто сделав ваши операторы преобразования явными или дайте им имена, такие как to_bool() и to_string().

+2

Член/участник не имеет значения. Это проблема. –

+0

Я согласен с вашим заключением (предпочитаю явные преобразования), но я не уверен в ваших объяснениях. См. [Этот пример на coliru] (http://coliru.stacked-crooked.com/a/174a94a1e2fdcb7e). – YSC

+0

@ T.C. Ах-ха! Благодарю. –