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
再次是我们可以在常量表达式中命名的局部变量。
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
再次是我们可以在常量表达式中命名的局部变量。