Являются ли явные операторы преобразования допустимыми в скопированных списках инициализаторов?

Следующий код компилируется с GCC 4.9.2, но не с Clang 3.5.0:

#include <string>

class Foo
{
public:
  explicit operator std::string() const;
};

std::string bar{Foo{}}; // Works in g++, fails in clang++
std::string baz(Foo{}); // Works in both

clang++ говорит:

foo.cpp:9:13: error: no matching constructor for initialization of 'std::string'
      (aka 'basic_string<char>')
std::string bar{Foo{}};
            ^  ~~~~~~~
...: note: candidate constructor not viable: no known conversion from 'Foo' to
      'const std::basic_string<char> &' for 1st argument
      basic_string(const basic_string& __str);
      ^

Любопытно, что он работает, если std::string заменен примитивным типом типа int.

Ответ 1

Кажется, это ошибка Clang. [Over.match.list]/1:

Когда объекты типа неагрегатного класса T инициализируются списком (8.5.4), разрешение перегрузки выбирает конструктор в две фазы:

  • [..]
  • Если не найден жизнеспособный конструктор списка инициализаторов, снова выполняется разрешение перегрузки, где все функции-кандидаты конструкторы класса T и список аргументов состоит из элементы списка инициализаторов.

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

Ответ 2

Из [class.conv.fct]/2:

Функция преобразования может быть явной (7.1.2), и в этом случае она рассматривается только как пользовательское преобразование для прямой инициализации (8.5).

Итак, вопрос заключается в том, как вы инициализируете свои объекты. Ясно, что baz имеет прямую инициализацию, поэтому это работает. Напротив, bar инициализируется прямым списком, но не является прямым инициализированным, поэтому явное преобразование недоступно.

Ответ 3

clang, похоже, не заботится о том, является ли оператор преобразования explicit или нет, и я считаю, что это правильно из-за формулировки в [over.best.ics].

Прежде всего, прямая инициализация

std::string baz(Foo{});

работает как с gcc, так и с clang и объясняется [class.conv.fct]/2, как указано в ответе KerrekSB.

Инициализация прямого списка

std::string bar{Foo{}};

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

Цитата N3337, §13.3.3.1/4 [over.best.ics]

Однако при рассмотрении аргумента конструктора или пользовательской функции преобразования, которая является кандидатом по 13.3.1.3 при вызове для копирования/перемещения временного кода на втором этапе класса копирование-инициализация, по 13.3.1.7 при передаче списка инициализатора в виде единственного аргумента или , когда список инициализаторов имеет ровно один элемент и преобразование в некоторый класс X или ссылку на (возможно, cv-квалификацию) X рассматривается для первого параметра конструктора X, или по 13.3.1.4, 13.3.1.5 или 13.3.1.6 во всех случаях рассматриваются только стандартные последовательности преобразования и последовательности преобразования многоточия.