Рассмотрим следующие два класса:
#define PRETTY(x) (std::cout << __PRETTY_FUNCTION__ << " : " << (x) << '\n')
struct D;
struct C {
C() { PRETTY(this);}
C(const C&) { PRETTY(this);}
C(const D&) { PRETTY(this);}
};
struct D {
D() { PRETTY(this);}
operator C() { PRETTY(this); return C();}
};
Мы заинтересованы в разрешении перегрузки между двумя конструкторами:
C::C(const C&);
C::C(const D&);
Этот код работает как ожидалось:
void f(const C& c){ PRETTY(&c);}
void f(const D& d){ PRETTY(&d);}
/*--------*/
D d;
f(d); //calls void f(const D& d)
так как void f(const D& d) является лучшим совпадением.
Но:
D d;
C c(d);
(как вы можете видеть здесь)
вызывает D::operator C() при компиляции с std=c++17 и вызывает C::C(const D&) с std=c++14 как для clang, так и для gcc HEAD.
Более того, это поведение недавно изменилось:
с clang 5, C::C(const D&) вызывается с std=c++17 и std=c++14.
Какое здесь правильное поведение?
Предварительное чтение стандарта (последний проект N4687):
C c(d) - это прямая инициализация, которая не является копией elision ([dcl.init]/17.6.1). [dcl.init]/17.6.2 сообщает нам, что соответствующие конструкторы перечислены и что лучший выбирается с помощью разрешения перегрузки. [over.match.ctor] сообщает, что применимые конструкторы в этом случае являются всеми конструкторами.
В этом случае: C(), C(const C&) и C(const D&) (без перемещения ctor). C() явно нежизнеспособен и, следовательно, отбрасывается из набора перегрузки. ([Over.match.viable])
Конструкторы не имеют неявного параметра объекта, поэтому C(const C&) и C(const D&) берут ровно один параметр. ([Over.match.funcs]/2)
Теперь переходим к [over.match.best]. Здесь мы находим, что нам нужно определить, какие
из этих двух неявных преобразований (ICS) лучше. ICS
C(const D&) включает только стандартную последовательность преобразования, но ICS C(const C&) включает пользовательскую последовательность преобразования.
Поэтому C(const D&) следует выбирать вместо C(const C&).
Интересно, что эти две модификации приводят к тому, что конструктор "right" назовем:
operator C() { /* */ } в operator C() const { /* */ }
или
C(const D&) { /* */ } в C(D&) { /* */ }
Это то, что произойдет (я думаю) в случае инициализации копирования, когда определяемые пользователем преобразования и конструкторы преобразования подвержены перегрузке разрешение.
Поскольку Коломбо рекомендует, чтобы я подал отчет об ошибке с gcc и clang