Конструктор constexpr дает другой результат при оценке во время компиляции GCC

Конструктор использует функцию, принимающую ссылку и возвращающую значение, при многократном изменении члена данных:

constexpr int   vv(int   x) {return x;}
constexpr int & rr(int & x) {return x;}
constexpr int   rv(int & x) {return x;}

constexpr struct S {
    int x {0};
    template<typename F> constexpr S(F f) {x = f(x) + 1; x = f(x) + 1;}
} s(rv); // s.x is 1 if function rv is used, 2 otherwise.
static_assert(s.x == 2, "");

Только функция rv дает неожиданный результат при использовании в конструкторе. Если вместо vv или rr передается s.x 2, как ожидалось.

Я заметил поведение в GCC 5.4.1 ARM и, похоже, во всех версиях GCC, поддерживающих С++ 14, одинаково. Во всех случаях Clang дает ожидаемый результат 2. Ни GCC, ни Clang не дают никаких предупреждений о включении Wall и Wextra.

Является ли этот пример допустимым С++ 14 и ошибкой в ​​GCC? Я прочитал список ограничений на постоянные выражения в стандарте и не вижу ничего очевидного, что это нарушает, но я не уверен, что понимаю все технические детали.

Ответ 1

Ваш код, безусловно, должен быть хорошо сформирован. Я считаю, что GCC выполняет пересмотренную форму memoization; назад в С++ 11, объекты не могли быть изменены в постоянных выражениях, следовательно, было отлично, что бы кешировать результаты функции. На самом деле, совершенно очевидно из нескольких отчетов об ошибках Clang, которые GCC сделал именно так, см., Например, здесь:

Clang constexpr не выполняет кэширование. В С++ 14, даже не ясно, будет ли кеширование возможным, так что это похоже на пустая трата времени, чтобы инвестировать в нее сейчас.

Очевидно, им пришлось ввести некоторый дополнительный анализ в С++ 14, и они допустили ошибку: они предположили, что любой подобъект объекта constexpr не может быть изменен в течение его жизненного цикла. Это явно не сохраняется в период строительства объекта-объекта. Если мы используем временную, а не x, компилируем код. Если мы поместим все это в функцию constexpr и удалим s constexpr specifier, он работает.

Как ни странно, результаты rr и vv также кэшируются, но возвращается одна и та же ссылка, и вызов по значению полностью избегает этой проблемы:)

Отмечено как 79520.