Изменение глобальной переменной в функции constexpr в С++ 17

В С++ 17 разрешено ли изменять глобальные переменные в функции constexpr?

#include <iostream>

int global = 0;

constexpr int Foo(bool arg) {
    if (arg) {
        return 1;
    }
    return global++;
}

int main() {
    std::cout << global;
    Foo(true);
    std::cout << global;
    Foo(false);
    std::cout << global;
}

Я бы не ожидал, что вы сможете, но clang 6 позволяет это: https://godbolt.org/g/UB8iK2

GCC, однако, не делает: https://godbolt.org/g/ykAJMA

Какой компилятор прав?

Ответ 1

Какой компилятор прав?

Кланг прав.

Определение constexpr функции в соответствии с dcl.constexpr/3

Определение функции constexpr должно удовлетворять следующим требованиям:

(3.1) его тип возврата должен быть буквальным;
(3.2) каждый из его типов параметров должен быть буквальным типом;
(3.3) его тело-функция должно быть = delete, = default или составной оператор, который не содержит:

(3.3.1) определение asm,
(3.3.2) утверждение goto,
(3.3.3) метку идентификатора,
(3.3.4) блок try, или
(3.3.5) определение переменной нелитерального типа или статической или длительности хранения потоков или для которой не выполняется инициализация.

Также согласно dcl.constexpr/5:

Для функции constexpr или конструктора constexpr, которая не является ни дефолтом, ни шаблоном, если значения аргументов не существуют, так что вызов функции или конструктора может быть оцененным подвыражением основного константного выражения,

Foo(true) может быть оценено с помощью выражения константы (т.е. 1).

Кроме того, Foo(false) может быть, но не требуется для постоянной оценки.

ЗАКЛЮЧЕНИЕ

Таким образом, ошибка в GCC.


Большое спасибо @Barry, @aschepler и @BenVoigt за помощь в ответе.

Ответ 2

Добавлю, что dcl.constexpr/5 дополнительно требует:

Для функции constexpr или конструктора constexpr, которая не является ни дефолтом, ни шаблоном, если значения аргументов не существуют, так что вызов функции или конструктора может быть оцененным подвыражением основного константного выражения или для конструктора постоянным инициализатором для некоторый объект ([basic.start.static]), программа плохо сформирована, не требуется диагностика.

Так как вы изящно написали функцию так, чтобы Foo(true) оценивал основное константное выражение, Foo(false) не требуется.