Подтвердить что ты не робот

Неоднозначность с использованием шаблонного оператора преобразования и неявного конструктора копирования

clang и gcc отличаются поведением для следующего кода:

struct foo
{
    foo(int);
};

struct waldo
{
    template <typename T>
    operator T();
};

int main()
{
    waldo w;
    foo f{w};
}

Этот код принимается clang, при вызове конструктора foo(int). Однако gcc жалуется на двусмысленность между конструктором foo(int) и неявно сгенерированными конструкциями копирования и перемещения:

test.cpp: In function 'int main()':
test.cpp:15:12: error: call of overloaded 'foo(<brace-enclosed initializer list>)' is ambiguous
     foo f{w};
            ^
test.cpp:15:12: note: candidates are:
test.cpp:3:5: note: foo::foo(int)
     foo(int);
     ^
test.cpp:1:8: note: constexpr foo::foo(const foo&)
 struct foo
        ^
test.cpp:1:8: note: constexpr foo::foo(foo&&)

Кто прав?

Интересно также отметить, что если foo f{w} изменено на foo f(w) (обратите внимание на изменение от скобок до скобок), как gcc, так и clang дают ошибку. Это заставляет меня надеяться, что поведение gcc для приведенного выше примера (т.е. Предоставление ошибки) является правильным, иначе возникнет странная несогласованность между формами инициализации () и {}.

EDIT: после предложения Kerrek SB я попробовал delete создать конструктор копирования foo:

struct foo
{
    foo(int);
    foo(const foo&) = delete;
};

Поведение остается неизменным.

4b9b3361

Ответ 1

Для инициализации списка, если в элементе списка есть один элемент (здесь w), и рассматривается конструктор класса X с параметром "ссылка на const/volatile X", никакие пользовательские преобразования которые считаются. Таким образом, нельзя использовать конструктор копирования и перемещения foo. Поэтому конструктор foo(int) однозначно выбирается.

Итак, Клэнг здесь прав.

РЕДАКТИРОВАТЬ: Для пользователей стандартов здесь см. 13.3.3.1p4