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
有效。< / p>
编辑2:第一个问题现在很清楚: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84421
clang是对的,GCC 7也接受了这个代码。该错误将在GCC 8的最终版本中修复。
答案 0 :(得分:4)
Clang在所有情况下都是正确的。 [完全披露:我是Clang开发者]
所有情况下的问题都归结为:我们可以在constexpr
中的常量表达式中调用v
成员函数吗?
要回答这个问题,我们需要查看[expr.const] p2,其中说:
表达式e是核心常量表达式,除非根据抽象机器(6.8.1)的规则评估e将评估以下表达式之一:
- ...
- id-expression ,引用引用类型的变量或数据成员,除非引用具有 在初始化和之前
- 使用常量表达式
初始化- 其生命周期始于
的评估范围内e
;- ...
其他规则都没有禁止您的任何示例。特别是,如果 允许在常量表达式中命名局部变量(如果它们不是引用类型)。 (你不允许对它们执行左值到右值的转换 - 也就是说,读取它们的值 - 除非它们的值已知(例如,因为它们是constexpr
),并且不允许您最终引用此类变量的地址,但 允许为其命名。)
引用类型实体的规则不同的原因是仅仅命名引用类型的实体会导致引用立即被解析,即使您不对结果执行任何操作,并且解析引用需要知道它必然会发生什么。
所以:第一个例子是有效的。 *this
成员函数的constexpr
值绑定到局部变量a
。我们不知道是什么对象并不重要,因为评估并不关心。
第二个例子(其中v
是参考类型)是不正确的。仅仅命名v
需要将其解析为它所绑定的对象,这不能作为常量表达式求值的一部分来完成,因为我们不知道它最终会被绑定到什么。后面的评估步骤不会使用结果对象并不重要;引用在命名时立即解决。
第三个例子的效果与第一个例子相同。值得注意的是,即使您将v
更改为引用类型,第三个示例仍然有效:
auto f = [](auto &v) { auto x = v; if constexpr (x) {} };
A a;
f(a);
...因为x
再一次是我们可以在常量表达式中命名的局部变量。