Подтвердить что ты не робот

Decltype() захваченной переменной в лямбда: ошибка GCC и/или ошибка Clang?

Я проверил GCC buglist и Clang buglist и пока ничего не вижу.

Эта ссылка Wandbox показывает код С++ 11/С++ 14, выполняющий decltype(x) и decltype((x)) для различных типов x, захваченный лямбдами. GCC и Clang дают разные ответы на этот код. Какие из них, если они есть, верны?

Здесь оскорбительный фрагмент:

// inside main()
int i = 42;
int &j = i;
[j=j](){
    static_assert(std::is_same<decltype(j), GCC(const) int>::value,""); // A
    static_assert(std::is_same<decltype((j)), const int&>::value,""); // B
}();
[=](){
    static_assert(std::is_same<decltype(j), int&>::value,""); // C
    static_assert(std::is_same<decltype((j)), CLANG(const) int&>::value,""); // D
}();

где:

#ifdef __clang__
 #define CLANG(x) x
 #define GCC(x)
#else
 #define GCC(x) x
 #define CLANG(x)
#endif

Я считаю, что в обоих случаях вещь, которая фактически захвачена (*) является (не const) int, инициализированной копией значения j (то есть, i). Поскольку лямбда не отмечена mutable, ее operator() будет функцией члена const. Исходя из этих предпосылок, позвольте продолжить...

В строке // A GCC сообщает мне, что decltype явно init-capture j равен const int, когда я почти уверен, что он должен быть int (за Clang).

В строке // B оба компилятора согласны с тем, что (j) является lvalue, относящимся к const int (поскольку лямбда не отмечена mutable); это имеет для меня смысл.

В строке // C оба компилятора согласны с тем, что j - это имя, относящееся к int&, объявленному в строке 2. Это является следствием 5.1.2 [expr.prim.lambda]/19, а точнее, является следствием действия-thing-that-when-when-this-clause-is-not-being. Внутри [=] лямбда имя j относится к j во внешней области видимости, но выражение (j) относится к (j), которое существовало бы, если бы j должны были быть захвачены. Я не совсем понимаю, как это работает или почему это желательно, но вот оно. Я готов указать, что это не ошибка в компиляторе.

В строке // D Клэнг говорит мне, что (j) является lvalue, относящимся к const int, тогда как GCC сообщает мне, что это lvalue, ссылаясь на неконстантный int. Я почти уверен, что Кланг прав, и GCC ошибается; decltype((j)) должен быть одинаковым, будет ли j зафиксирован неявно или явно.

Итак:

  • Являются ли мои объяснения правильными (в соответствии со стандартом)?
  • Изменяется ли правильный ответ между С++ 11 и С++ 14 (и С++ 1z)?
  • Являются ли // A и // D обе ошибки в GCC?
  • Были ли уже зарегистрированы эти ошибки?

(*) - на самом деле ничто не технически захватывается второй лямбда, потому что оно не использует j в любом оцениваемом контексте. Поэтому строки // A и // C дают разные ответы. Но я не знаю никакой красивой терминологии для того, что-бы-быть-сделано-до-j, поэтому я просто говорю "захвачен".

4b9b3361

Ответ 1

Я считаю, что оба компиляторы ошибочны для (A) и gcc неправильно для (D).Забастовкa >
Я считаю, что gcc неправильно для (A) и (D), в то время как clang является правильным для обоих.


Соответствующие разделы [expr.lambda.prim]:

Выполнение init-capture ведет себя так, как будто оно объявляет и явно фиксирует переменную формы "auto init-capture"; чья декларативная область является составным выражением лямбда-выражений, за исключением того, что:
- если захват копируется (см. Ниже), нестатический член данных, объявленный для захвата, и переменная рассматривается как два разных способа обращения к одному и тому же объекту, который имеет время жизни нестатический элемент данных, и никакая дополнительная копия и уничтожение не выполняются,

и

Каждое id-выражение внутри составного оператора лямбда-выражения , которое является неприемлемым (3.2) объект, захваченный копией, преобразуется в доступ к соответствующему неназванному элементу данных тип закрытия.

decltype(j) не является нечетным использованием j, поэтому такое преобразование не следует рассматривать. Таким образом, в случае [=]{...}, decltype(j) должен давать int&. Однако в случае init-capture поведение выглядит так, как если бы существовала переменная формы auto j = j;, а переменная j относится к одному и тому же неназванному нестатическому элементу данных без необходимости такого преобразования. Поэтому в случае [j=j]{...}, decltype(j) должен указывать тип этой переменной - int. Это определенно не const int. Это ошибка.

Следующий соответствующий раздел:

Каждое вхождение decltype((x)), где x - это, возможно, заключенное в скобки id-выражение, которое называет сущность с продолжительностью автоматического хранения, обрабатывается так, как если бы x были преобразованы в доступ к соответствующему элементу данных типа закрытия, было бы объявлено, если x было odr-использованием обозначенного объекта. [Пример:

void f3() {
    float x, &r = x;
    [=] {                       // x and r are not captured (appearance in a decltype operand is not an odr-use)
        decltype(x) y1;         // y1 has type float
        decltype((x)) y2 = y1;  // y2 has type float const& because this lambda
                                // is not mutable and x is an lvalue
        decltype(r) r1 = y1;    // r1 has type float& (transformation not considered)
        decltype((r)) r2 = y2;  // r2 has type float const&
    }
}

-end пример]

В примере далее показано, что decltype(j) должен быть int& в неявном экземпляре копии, а также демонстрирует, что decltype((j)) обрабатывается так, как если бы x были соответствующим членом данных, который был бы объявлен: который равен int const& в обоих случаях (поскольку лямбда не mutable и j - значение l). Ваши (C) и (D) случаи точно отражают объявления r1, r2 в примере. Который, хотя примеры не являются нормативными, безусловно, предполагает, что gcc ошибается в том, что имеет другое поведение.