Шаблонный делегирующий конструктор копирования в константных выражениях - PullRequest
6 голосов
/ 01 марта 2020

Этот вопрос мотивируется этим .

Рассмотрим следующий код:

struct B {};

struct S {
    B b; // #1

    S() = default;

    template <typename ...dummy> // #2
    constexpr S(const S&) {}

    template <typename ...dummy> // #3
    constexpr S(S &other) 
        : S(const_cast<const S&>(other)) // #4
    {}
};

S s;
constexpr S f() {return s;}

int main() {
    constexpr auto x = f();
}

G CC успешно компилирует этот код, но Clang отклоняет его ( Пример на Godbolt.org ). Clang выдает сообщение об ошибке:

<source>:21:20: error: constexpr variable 'x' must be initialized by a constant expression
    constexpr auto x = f();
                   ^   ~~~
<source>:13:11: note: read of non-constexpr variable 's' is not allowed in a constant expression
        : S(const_cast<const S&>(other)) 
          ^
<source>:13:11: note: in call to 'S(s)'
<source>:18:25: note: in call to 'S(s)'
constexpr S f() {return s;}
                        ^
<source>:21:24: note: in call to 'f()'
    constexpr auto x = f();
                       ^
<source>:17:3: note: declared here
S s;
  ^

Обратите внимание: если мы удалим любой из # 2, # 3 или # 4, оба компилятора примут этот код. Если мы заменим # 1 на int b = 0;, , то оба компилятора отклонят его .

Мой вопрос:

  1. Какой компилятор корректен в соответствии с текущим стандартом?
  2. Если G CC верен, почему замена # 1 на int b = 0; делает этот код некорректным? Если Clang верен, почему удаление любого из # 2, # 3 или # 4 делает этот код правильно сформированным?

1 Ответ

3 голосов
/ 02 марта 2020

Поскольку оба ваших пользовательских конструктора являются шаблонами, они не являются конструкторами копирования (или перемещения). Таким образом, компилятор неявно объявляет конструктор копирования и определяет его как значение по умолчанию.

Часть 1, таким образом, сводится к следующей отличительной программе:

struct A {
    struct B {} b;
    constexpr A() {};
    // constexpr A(A const& a) : b{a.b} {}    // #1
};
int main() {
    auto a = A{};
    constexpr int i = (A{a}, 0);
}

Отклонено от Clang и MSV C, принятые gcc; раскомментируйте #1 для всех трех, чтобы принять.

Согласно определению неявно определенного конструктора копирования , нет никакого способа, которым #1 отличается от constexpr A(A const&) = default;, поэтому g cc правильно. Также обратите внимание, что если мы дадим B определяемый пользователем конструктор копии constexpr, Clang и MSV C снова примут, поэтому проблема заключается в том, что эти компиляторы не могут отслеживать конструктивность копии constexpr рекурсивно пустых неявно копируемых классов. Поданные ошибки для MSV C и Clang ( исправлено для Clang 11).

Часть 2:

Удаление #1 означает, что вы копируете (выполняете преобразование lvalue в rvalue) объект s.b типа int, время жизни которого начинается вне контекста constexpr.

Удаление #2 дает S определяемый пользователем constexpr конструктор копирования, который затем делегируется в #4.

Удаление #3 дает S определяемый пользователем (неконстантный) конструктор копирования, подавляя неявно- определенный конструктор копирования, поэтому делегирующая конструкция вызывает конструктор const шаблона (который, помните, не является конструктором копирования).

Удаление #4 означает, что ваш шаблон конструктора с аргументом S& other больше не вызывает неявно -определенный конструктор копирования, поэтому b инициализируется по умолчанию, что Clang может делать в контексте constexpr. Обратите внимание, что конструктор копирования все еще неявно объявляется и определяется как дефолтный, просто ваш конструктор template<class...> S::S(S& other) предпочитается разрешением перегрузки.

Важно признать различие между , подавляющим неявно определенный конструктор копирования и предоставление предпочтительной перегрузки. template<class...> S::S(S&) не подавляет неявно определенный конструктор копирования, но предпочтительнее для неконстантного аргумента lvalue, при условии, что неявно определенный конструктор копирования имеет аргумент S const&. С другой стороны, template<class...> S::S(S const&) не подавляет неявно определенный конструктор копирования и никогда не может быть предпочтительнее неявно определенного конструктора копирования, поскольку это шаблон и списки параметров одинаковы.

...