Неявный приоритет оператора преобразования

В следующем фрагменте кода (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<<, но есть все это? Стандарт ли что-то говорит о выборе конкретного неявного преобразования?

Ответ 1

Вспомним, что std::string не является автономным типом, он действительно специализируется на шаблоне шаблона - std::basic_string<char>. Очень важная деталь заключается в том, что потенциальная перегрузка для потоковой передачи a std::string не принимает аргумент std::string const&, это шаблон функции, который выводит a 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

Ответ 2

ostream& operator<<( bool value );

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

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

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


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

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

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

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