constexpr 表达式和变量生存期,一个 g++ 和 clang 不一致的例子

Posted

技术标签:

【中文标题】constexpr 表达式和变量生存期,一个 g++ 和 clang 不一致的例子【英文标题】:constexpr expression and variable lifetime, an example where g++ and clang disagree 【发布时间】:2017-11-13 17:16:20 【问题描述】:

考虑一下简单的 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 哪个是正确的?有什么问题?

【问题讨论】:

【参考方案1】:

我的解释是 clang++ 是对的,而 g++ 太放纵了

我们可以在标准https://isocpp.org/std/the-standard(可以下载草稿,attention big PDF!)中找到一个紧密的示例([expr.const] 部分,第 126 页)。

constexpr int g(int k)  
    constexpr int x = incr(k); 

    return x; 

其中解释为:

错误:incr(k) 不是核心常量表达式,因为 k 的生命周期开始于表达式 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); 

【讨论】:

我相信这个答案是正确的。为了增加支持,当 sizeuse_size 的参数的生命周期开始于函数范围内时,Clang 和 MSVC 接受代码:Demo @AndyG 我不同意,因为引用的非规范示例只是演示了规则 [expr.const] ¶2.7.4 “表达式 e核心常量表达式 i> 除非 ... [it] 将评估... 一个左值到右值的转换,除非... [it] 指的是一个非易失性对象,其生命周期始于对 e" 的评估。该示例是一个错误,因为incr(k) 涉及左值到右值的转换(因为它增加了左值),而size(foo) 没有(因为它只是将左值引用绑定到左值)。 Demo @Oktalist:感谢您的回复。我更多地考虑 [expr.const] 中的部分内容,即“表达式 e 是核心常量表达式,除非...... [它是] id-expression 引用引用类型的变量或数据成员,除非引用有一个先前的初始化,或者它是用一个常量表达式初始化的,或者它的生命周期在 e 的评估中开始;" OPs 示例处理引用绑定并且是一个错误,因为引用的生命周期未使用常量表达式初始化或以 e 的评估开始 @AndyG 谢谢,我错过了。分歧被撤回。【参考方案2】:

我原以为 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);

Live example

【讨论】:

这是我的第一个想法。确实,编译器拥有在编译时知道 n 值所需的一切。但是,我认为由于我在“解释”中提供的原因,该标准禁止这样做。 是的,仔细阅读规则,我认为你是对的。在某些情况下,引用不能很好地与 constexpr 配合使用

以上是关于constexpr 表达式和变量生存期,一个 g++ 和 clang 不一致的例子的主要内容,如果未能解决你的问题,请参考以下文章

变量的生存期和存储分配

C语言变量的作用域和生存期问题

php变量的作用域和生存期

指向函数本地结构的指针的范围和生存期

8——对象的作用域,生存期,……

C变量作用域,生存期,链接特性