переменная not-constexpr в if constexpr - clang vs. GCC

struct A{
    constexpr operator bool()const{ return true; }
};

int main(){
    auto f = [](auto v){ if constexpr(v){} };
    A a;
    f(a);
}

clang 6 принимает Код, GCC 8 отклоняет его:

$ g++ -std=c++17 main.cpp
main.cpp: In lambda function:
main.cpp:6:37: error: 'v' is not a constant expression
  auto f = [](auto v){ if constexpr(v){} };
                                     ^

Кто прав и почему?

Когда я беру параметр для ссылки, оба отклоняют код:

struct A{
    constexpr operator bool()const{ return true; }
};

int main(){
    auto f = [](auto& v){ if constexpr(v){} };
    constexpr A a;
    f(a);
}

Скомпилировано с помощью clang 6:

$ clang++ -std=c++17 main.cpp
main.cpp:6:40: error: constexpr if condition is not a constant expression
    auto f = [](auto& v){ if constexpr(v){} };
                                       ^
main.cpp:8:6: note: in instantiation of function template specialization 
    'main()::(anonymous class)::operator()<const A>' requested here
    f(a);
     ^
1 error generated.

Когда я копирую параметр в локальную переменную, оба принимают код:

struct A{
    constexpr operator bool()const{ return true; }
};

int main(){
    auto f = [](auto v){ auto x = v; if constexpr(x){} };
    A a;
    f(a);
}

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

В первом случае я подозреваю, что clang прав, потому что он похож на второй. Я хотел бы знать, правильны ли в первом случае clang или GCC и какие правила во втором случае делают использование переменной not-constexpr v недействительной, а в третьем случае x действительным.

Изменить 2: теперь первый вопрос ясен: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84421

clang был прав, GCC 7 тоже принял код. Ошибка будет исправлена ​​в финальной версии GCC 8.


person Benjamin Buch    schedule 16.02.2018    source источник
comment
Вы спрашиваете, является ли v постоянным выражением или почему непостоянное выражение нельзя использовать там, где оно требуется? Какие исследования вы провели по этой теме?   -  person user167921    schedule 16.02.2018
comment
Я думаю, Кланг прав. Вы можете, например, передать std::bool_constant, который, хотя и не constexpr, имеет оператор преобразования constexpr в bool.   -  person Quentin    schedule 16.02.2018
comment
Похоже на обман этого?   -  person Barry    schedule 16.02.2018
comment
Да, очень похоже, спасибо!   -  person Benjamin Buch    schedule 16.02.2018
comment
Возможный дубликат Общая лямбда и ее аргумент как постоянное выражение   -  person Benjamin Buch    schedule 16.02.2018
comment
@BenjaminBuch: Я не думаю, что это дубликат, который сосредоточен на вызове функции внутри lamdba (а аргумент, как известно, не был constexpr)   -  person Ben Voigt    schedule 16.02.2018
comment
@BenVoigt: В моем вопросе вызывается (явно) оператор преобразования constexpr в bool в упомянутом (неявно) конструкторе копирования constexpr; правила должны быть такими же, если я правильно понимаю. : - /   -  person Benjamin Buch    schedule 17.02.2018
comment
@BenjaminBuch: В другом аргумент этой функции не используется. Следовательно, ожидается, что результатом функции будет constexpr, даже если аргумент - нет.   -  person Ben Voigt    schedule 17.02.2018


Ответы (1)


Clang верен во всех случаях. [Полное раскрытие: я разработчик Clang]

Во всех случаях вопрос сводится к следующему: можем ли мы вызвать функцию-член constexpr на v в константном выражении?

Чтобы ответить на этот вопрос, нам нужно посмотреть на [expr.const] p2, в котором говорится:

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

  • ...
  • an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
    • it is initialized with a constant expression or
    • его время жизни началось в пределах оценки e;
  • ...

Ни одно из других правил не запрещает ваши примеры. В частности, вам разрешено давать имена локальным переменным в константном выражении, если они не относятся к ссылочному типу. (Вам не разрешено выполнять для них преобразование lvalue-to-rvalue, то есть читать их значения, если их значение не известно (например, потому что они constexpr), и вы не разрешено в конечном итоге ссылаться на адрес такой переменной, но вам разрешено давать им имена.)

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

Итак: верен первый пример. Значение *this функции-члена constexpr привязано к локальной переменной a. Не имеет значения, что мы не знаем, что это за объект, потому что оценке все равно.

Второй пример (где v относится к ссылочному типу) плохо сформирован. Простое присвоение имени v требует преобразования его в объект, к которому он привязан, что не может быть выполнено как часть оценки константного выражения, потому что мы не знаем, к чему это в конечном итоге будет привязано. Не имеет значения, что на последующих этапах оценки полученный объект не будет использоваться; ссылки разрешаются сразу после того, как они были названы.

Третий пример действителен по той же причине, что и первый. Примечательно, что третий пример остается действительным, даже если вы измените v на ссылочный тип:

auto f = [](auto &v) { auto x = v; if constexpr (x) {} };
A a;
f(a);

... потому что x, опять же, локальная переменная, которую мы можем назвать внутри константного выражения.

person Richard Smith    schedule 17.02.2018