C++ constexpr表达式和变量生存期,例如g++;和叮当声不同意

C++ constexpr表达式和变量生存期,例如g++;和叮当声不同意,c++,c++11,g++,clang++,C++,C++11,G++,Clang++,考虑一下简单的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() {

考虑一下简单的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++版本5.3.1和7.2.1以及clang++版本3.6.2和5.0.0检查了前面的语句)


我的问题:g++或clang中哪一个是正确的?问题出在哪里?

我的解释是,clang++是正确的,而g++过于宽容

我们可以在标准中找到一个相近的示例([expr.const]部分,第126页)(可以下载草稿,)

其中解释为:

错误:incr(k)不是核心常量表达式,因为 k的寿命开始于表达式incr(k)之外

这正是带有
foo
参数的
use\u 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); }
模板
constexpr int size(const Foo&){return N;}
模板
void use_size(const Foo&Foo){constexpr int n=size(Foo);}

在这种情况下,我原以为克朗是错的。它应该将函数调用作为常量表达式进行计算,因为您只使用模板参数,而不使用对象本身。由于在
constexpr
函数中不使用对象,因此不应禁止编译时求值

然而,标准中有一条规则规定,在常量表达式(如引用)之前开始其生命周期的对象不能用作constexpr

在这种情况下有一个简单的解决方法。我认为它不喜欢参考:

template <int N> // pass by value, clang is happy
void use_size(Foo<N> foo) { constexpr int n = size(foo); }
template//passby value,clang很高兴
void use_size(Foo Foo){constexpr int n=size(Foo);}
这里有一个

或者,也可以复制foo对象并使用该本地对象:

template <int N>
void use_size(const Foo<N>& foo) {
    auto f = foo;
    constexpr int n = size(f);
}
模板
无效使用大小(常量Foo和Foo){
自动f=foo;
constexpr int n=大小(f);
}

我相信这个答案是正确的。为了增加支持,当
size
use_size
的参数的生存期在函数范围内开始时,Clang和MSVC接受代码:@AndyG我不同意,因为引用的非规范性示例只是演示了规则[expr.const]^2.7.4“表达式
e
是一个核心常量表达式,除非…[it]将计算…左值到右值的转换,除非…[它]指的是一个非易失性对象,其生存期开始于
e
”的计算。该示例是一个错误,因为
incr(k)
涉及左值到右值的转换(因为它增加左值),而
size(foo)
不涉及左值(因为它仅将左值引用绑定到左值)@Oktalist:谢谢你的回复。我更多的是按照[expr.const]中的那部分思路思考,“表达式e是一个核心常量表达式,除非…[它是]引用引用类型的变量或数据成员的id表达式,除非引用之前有一个初始化,或者它是用常量表达式初始化的,或者它的生存期是从e的求值开始的;”OPs示例处理引用绑定,这是一个错误,因为引用的生存期没有用常量表达式初始化,也没有从e@AndyG谢谢,我错过了。这是我的第一个想法。的确,编译器拥有在编译时知道n值所需的一切。然而,我认为标准禁止这样做是因为我在“解释”中提供了原因。是的,仔细阅读规则,我认为你是对的。在某些情况下,引用不能很好地与constexpr配合使用
template <int N> // pass by value, clang is happy
void use_size(Foo<N> foo) { constexpr int n = size(foo); }
template <int N>
void use_size(const Foo<N>& foo) {
    auto f = foo;
    constexpr int n = size(f);
}