Почему конструктор A
- лучший выбор в первом случае? Оператор преобразования B
кажется более подходящим, поскольку он не требует неявного преобразования из B<int>
в A<int>
.
Я считаю, что этот выбор обусловлен открытым стандартом отчет о проблеме CWG 2327 :
2327. Копирование разрешения для прямой инициализации с функцией преобразования
Раздел: 11.6 [dcl.init]
Статус: черновик
Отправитель: Ричард Смит
Дата: 2016-09-30
Рассмотрим пример, например:
struct Cat {};
struct Dog { operator Cat(); };
Dog d;
Cat c(d);
Это относится к 11.6 [dcl.init], пулю 17.6.2: [...]
Разрешение перегрузки выбирает конструктор перемещения Cat
. Инициализация параметра Cat&&
конструктора приводит к временному, согласно 11.6.3 [dcl.init.ref] подпункту 5.2.1.2. Это исключает возможность исключения копии в данном случае.
Это, по-видимому, недосмотр в изменении формулировки для гарантированного исключения копии. Предположительно, мы должны быть одновременно с учетом как конструкторов, так и функций преобразования в этом случае, как и для инициализации копирования , но нам нужно убедиться, что это не создает каких-либо новых проблем или двусмысленностей.
Можно отметить, что и G CC, и Clang выбирают оператор преобразования (хотя проблема еще не решена) из версий 7.1 и 6.0 соответственно (для уровня языка C ++ 17); до этих выпусков и G CC, и Clang выбирали перегрузку A<X>::A(A<U> &&) [T = X, U = int]
ctor.
Почему первый и второй случаи дают разные результаты? Что изменилось в C ++ 17?
В C ++ 17 введено гарантированное исключение копирования, что означает, что компилятор должен опускать копирование и перемещение объектов класса (даже если они иметь побочные эффекты) при определенных обстоятельствах; если аргумент в вышеупомянутом вопросе верен, это такое обстоятельство.
Примечательно, что G CC и Clang оба перечисляют неизвестный (/ или нулевой) статус CWG 2327; возможно, поскольку проблема в том, что он все еще в статусе Черновик .
C ++ 17: гарантированное исключение копирования / перемещения и совокупная инициализация конструкторов, объявленных пользователем
Следующая программа правильно сформирована на C ++ 17:
struct A {
A() = delete;
A(const A&) = delete;
A(A&&) = delete;
A& operator=(const A&) = delete;
A& operator=(A&&) = delete;
};
struct B {
B() = delete;
B(const B&) = delete;
B(B&&) = delete;
B& operator=(const B&) = delete;
B& operator=(B&&) = delete;
operator A() { return {}; }
};
int main ()
{
//A a; // error; default initialization (deleted ctor)
A a{}; // OK before C++20: aggregate initialization
// OK int C++17 but not C++20:
// guaranteed copy/move elision using aggr. initialization
// in user defined B to A conversion function.
A a1 (B{});
}
который может стать сюрпризом. Основное правило здесь состоит в том, что и A
, и B
являются агрегатами (и, таким образом, могут быть инициализированы посредством агрегатной инициализации), поскольку они не содержат только конструкторы user- при условии (явно удалены) user- объявлено единиц.
C ++ 20 гарантированное исключение копирования / перемещения и более строгие правила для агрегированной инициализации
Начиная с P1008R1 , который имеет был принят для C ++ 20, приведенный выше фрагмент неверно сформирован, поскольку A
и B
больше не являются агрегатами, поскольку для них объявлены user- ctors; до P1008R1 требования были слабее, и только для типов не было предоставлено user- ctors.
Если мы объявляем A
и B
с явно заданными по умолчанию определениями, программа естественно имеет правильный формат.
struct A {
A() = default;
A(const A&) = delete;
A(A&&) = delete;
A& operator=(const A&) = delete;
A& operator=(A&&) = delete;
};
struct B {
B() = default;
B(const B&) = delete;
B(B&&) = delete;
B& operator=(const B&) = delete;
B& operator=(B&&) = delete;
operator A() { return {}; }
};
int main ()
{
// OK: guaranteed copy/move elision.
A a1 (B{});
}