constexpr 表达式和变量生命周期,g++ 和 clang 不一致的例子
constexpr expression and variable lifetime, an example where g++ and clang disagree
考虑简单的 C++11 代码:
template<int N>
struct Foo {};
template <int N>
constexpr int size(const Foo<N>&) { return N; }
template <int N>
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }
int main()
{
Foo<5> foo;
constexpr int x = size(foo); // works with gcc and clang
// _but_
use_size(foo); // the same statement in the use_size()
// function _only_ works for gcc
}
我可以用g++ -std=c++11 foo.cpp
编译成功
但是如果我使用 clang++,clang++ -std=c++11 foo.cpp
我会得到
foo.cpp:15:28: error: constexpr variable 'n' must be initialized by a constant expression
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }
~~~~~^~~~
foo.cpp:23:5: note: in instantiation of function template specialization 'use_size<5>' requested here
use_size(foo); // the same statement in the use_size()
^
1 error generated.
(nb: 编译器版本。我已经用 g++ 版本 5.3.1 和 7.2.1 以及 clang++ 版本 3.6.2 和 5.0.0 检查了前面的语句)
我的问题:g++和clang哪个对?有什么问题?
我的解释是clang++是对的,g++太宽松了。
我们可以在标准 https://isocpp.org/std/the-standard (draft can be downloaded, attention big PDF! 中找到一个相似的示例([expr.const] 部分,第 126 页)。
constexpr int g(int k) {
constexpr int x = incr(k);
return x;
}
其中解释说:
error: incr(k) is not a core constant expression because
lifetime of k began outside the expression incr(k)
这正是带有 foo
参数的 use_size()
函数中发生的情况,即使 size()
函数 仅 使用N
模板参数。
template <int N>
constexpr int size(const Foo<N>&) { return N; }
template <int N>
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }
我原以为 Clang 在这种情况下是错误的。它应该将您的函数调用评估为常量表达式,因为您只使用模板参数,而不是对象本身。由于您不在 constexpr
函数中使用该对象,因此应该没有禁止编译时评估的内容。
但是,标准中有一条规则规定,在常量表达式(例如引用)之前开始其生命周期的对象不能用作 constexpr。
在这种情况下有一个简单的修复方法。我认为它不喜欢参考:
template <int N> // pass by value, clang is happy
void use_size(Foo<N> foo) { constexpr int n = size(foo); }
这是一个live example
或者,您也可以复制 foo 对象并使用该本地对象:
template <int N>
void use_size(const Foo<N>& foo) {
auto f = foo;
constexpr int n = size(f);
}
考虑简单的 C++11 代码:
template<int N>
struct Foo {};
template <int N>
constexpr int size(const Foo<N>&) { return N; }
template <int N>
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }
int main()
{
Foo<5> foo;
constexpr int x = size(foo); // works with gcc and clang
// _but_
use_size(foo); // the same statement in the use_size()
// function _only_ works for gcc
}
我可以用g++ -std=c++11 foo.cpp
但是如果我使用 clang++,clang++ -std=c++11 foo.cpp
我会得到
foo.cpp:15:28: error: constexpr variable 'n' must be initialized by a constant expression
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }
~~~~~^~~~
foo.cpp:23:5: note: in instantiation of function template specialization 'use_size<5>' requested here
use_size(foo); // the same statement in the use_size()
^
1 error generated.
(nb: 编译器版本。我已经用 g++ 版本 5.3.1 和 7.2.1 以及 clang++ 版本 3.6.2 和 5.0.0 检查了前面的语句)
我的问题:g++和clang哪个对?有什么问题?
我的解释是clang++是对的,g++太宽松了。
我们可以在标准 https://isocpp.org/std/the-standard (draft can be downloaded, attention big PDF! 中找到一个相似的示例([expr.const] 部分,第 126 页)。
constexpr int g(int k) {
constexpr int x = incr(k);
return x;
}
其中解释说:
error: incr(k) is not a core constant expression because lifetime of k began outside the expression incr(k)
这正是带有 foo
参数的 use_size()
函数中发生的情况,即使 size()
函数 仅 使用N
模板参数。
template <int N>
constexpr int size(const Foo<N>&) { return N; }
template <int N>
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }
我原以为 Clang 在这种情况下是错误的。它应该将您的函数调用评估为常量表达式,因为您只使用模板参数,而不是对象本身。由于您不在 constexpr
函数中使用该对象,因此应该没有禁止编译时评估的内容。
但是,标准中有一条规则规定,在常量表达式(例如引用)之前开始其生命周期的对象不能用作 constexpr。
在这种情况下有一个简单的修复方法。我认为它不喜欢参考:
template <int N> // pass by value, clang is happy
void use_size(Foo<N> foo) { constexpr int n = size(foo); }
这是一个live example
或者,您也可以复制 foo 对象并使用该本地对象:
template <int N>
void use_size(const Foo<N>& foo) {
auto f = foo;
constexpr int n = size(f);
}