Рассмотрим следующие два класса:
#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