if constexpr 中的 not-constexpr 变量——clang 与 GCC

not-constexpr variable in 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 的最终版本中修复。

Clang 在所有情况下都是正确的。 [完全披露:我是一名 Clang 开发人员]

所有情况下的问题都归结为:我们可以在常量表达式中调用 v 上的 constexpr 成员函数吗?

要回答这个问题,我们需要看一下 [expr.const]p2,它说:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.8.1), would evaluate one of the following expressions:

  • ...
  • 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
    • its lifetime began within the evaluation of e;
  • ...

None 的其他规则禁止您的任何示例。特别是,如果局部变量不是引用类型,您 允许在常量表达式中命名局部变量。 (您 不允许 对它们执行 lvalue-to-rvalue 转换——也就是说,读取它们的值——除非它们的值是已知的(例如,因为它们是 constexpr),并且不允许您最终引用此类变量的地址,但您 允许为它们命名。)

引用类型实体的规则不同的原因是,仅命名引用类型的实体会导致引用立即解析,即使您不对结果做任何事情,解析引用也是如此需要知道它绑定到什么。

所以:第一个例子是有效的。 constexpr 成员函数的 *this 值绑定到局部变量 a。我们不知道那是什么对象并不重要,因为评估不关心。

第二个例子(其中v是引用类型)是ill-formed。仅命名 v 需要将其解析为它所绑定的对象,这不能作为常量表达式评估的一部分来完成,因为我们不知道它最终会被绑定到什么。后面的评估步骤不会使用结果对象并不重要;引用在命名时立即解析。

第三个示例与第一个示例的理由相同。值得注意的是,即使您将 v 更改为引用类型,第三个示例仍然有效:

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

... 因为 x 再次是我们可以在常量表达式中命名的局部变量。