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

GCC и Clang не согласны с С++ 17 constexpr lambda capture

Рассмотрим этот пример, который объявляет переменную как constexpr, захватывает ее копией в лямбда и объявляет другую переменную constexpr, которая является результатом функции constexpr, которая разворачивает нестандартный шаблонный параметр из исходной переменной.

#include <utility>

template<int I>
constexpr auto unwrap(std::integral_constant<int, I>) {
  return I;
}

int main() {
  constexpr auto i = std::integral_constant<int, 42>{};
  constexpr auto l = [i]() {
    constexpr int x = unwrap(i);
  };
}

Clang (trunk) принимает этот код. (wandbox)

Ошибка GCC (trunk) со следующим сообщением об ошибке (wandbox):

lambda_capture.cpp:11:31: error: the value of ‘i’ is not usable in a constant expression
     constexpr int x = unwrap(i);
                               ^
lambda_capture.cpp:10:28: note: ‘i’ was not declared ‘constexpr’
   constexpr auto l = [i]() {

Какой компилятор прав? Мне кажется, что это ошибка GCC, где constessprness lambda capture не правильно распространяется на лямбда-контекст.

4b9b3361

Ответ 1

Обе реализации прослушиваются, но я склонен думать, что GCC получил правильный ответ здесь.


Снижение захвата i заставляет Clang отказаться от компиляции кода. Это означает, что у него явно есть ошибка.

[expr.const]/2.12:

Выражение e является выражением постоянной константы, если оценка из e, следуя правилам абстрактной машины, будет оценивать одно из следующих выражений:

  • [...]
  • в лямбда-выражении, ссылка на [...] переменная с автоматической продолжительностью хранения, определенная вне этого лямбда-выражения, где ссылка будет использовать odr;
  • [...]

Поведение Клана шизофренично: если использование i в теле не является неприемлемым, то его не нужно захватывать, но он отвергает код в OP, если явный захват удален; OTOH, если это одностороннее использование, то приведенное выше unwrap(i) не является постоянным выражением, поэтому оно должно отклонять инициализацию x.


Реализация лямбда GCC является ужасно плохим в отношении использования odr. Он постоянно складывается ультра-ранним, в результате чего возникают всевозможные тонкие озорства. С другой стороны, для явных захватов он преобразует все виды использования, независимо от того, действительно ли он использует odr. Агрессивное постоянное складывание означает, что он принимает код OP, если захват i удален.

Предполагая, что unwrap(i) использует odr-use i, то верно, что в [expr.const]/2.12 код OP плохо сформирован.


Использует ли unwrap(i) odr-use i? Этот вопрос сводится к тому, чтобы считать, что инициализация копирования объекта параметра unwrap считается применением преобразования lvalue-to-rval в i, Я не вижу ничего в стандарте, который явно говорит о том, что здесь применяется преобразование lvalue-rvalue, а вместо этого [dcl.init]/17.6. 2 указывает, что мы вызываем конструктор (в данном случае тривиальный неявно определенный конструктор копирования), передающий i в качестве аргумента, связанного с его параметром, а привязка ссылок является классическим примером использования odr.

Конечно, применение преобразования l-to-r приведет к инициализации копии объекта integral_constant<int, 42> из i, но проблема здесь в том, что ничто в стандарте не говорит обратное - что все копии -инициализации объекта integral_constant<int, 42> из i рассчитываются как преобразования l-to-r.