为什么引用不能与编译时函数一起使用?

Why references can't be used with compile time functions?

我有两个片段。

第一个片段:

#include <string>

template <typename T>
constexpr bool foo(T&&) {
    return false;
}

int main() {
    std::string a;
    if constexpr (foo(a)) {
    }
}

第二个片段:

#include <string>

template <typename T>
constexpr bool foo(T&&) {
    return false;
}

int main() {
    std::string a;
    std::string& x = a;
    if constexpr (foo(x)) {
    }
}

第一个编译通过,但是第二个编译不通过(报错信息:error: 'x'的值在常量表达式中不可用。为什么?为什么a 可用于常量表达式而 x 不可用?

命令,用于编译g++ -std=c++17 main.cpp

因为你无法在编译时知道 x 会有什么值,你所知道的就是它将指向 a。你所做的是检查x的"value",但x的值是a所在的地址,你无法知道a在哪里将被分配,地址也不是常量。

另一方面,您已经知道 a 的值,它是一个空值 std::string。

这个问题包含更多细节:

因为常量表达式通常无法评估引用具有自动存储持续时间的对象的引用。这里我指的是"evaluate"通过判断对象的identity不是通过判断对象的值。因此,即使您的示例中不需要对象 a 的值(即没有应用左值到右值的转换),foo(x) 仍然不是常量表达式。

注意 foo(a) 不评估任何参考。尽管 foo 的参数是一个引用,但它不作为表达式求值。其实就算评价了,比如

template <typename T>
constexpr bool foo(T&& t) {
    t;
    return false;
}

foo(a) 仍然是一个常量表达式。这种情况是例外,因为引用 t 在 对 foo(a) 的评估中被初始化


标准中相关部分(不相关部分被我删掉):

[expr.const]/2:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, 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;

  • ...

[expr.const]/6:

A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), ... An entity is a permitted result of a constant expression if it is an object with static storage duration that is either not a temporary object or is a temporary object whose value satisfies the above constraints, or it is a function.

首先,在您的两个代码部分中,求值上下文都要求后缀表达式是常量表达式。函数模板foo的定义满足constexpr function的要求,因为它的参数是字面量类型。 id-expression ax 都是泛左值,它的评估决定了一个对象的身份,但唯一的区别是,在你的拳头代码中。从a复制初始化参数只需要进行身份转换,因为有如下规则:

When a parameter of reference type binds directly to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion

它什么都不做,因为
[over.ics.scs#2]

As described in Clause [conv], a standard conversion sequence is either the Identity conversion by itself (that is, no conversion)

剩下的表达式都是常量表达式。所以,对于foo(a)这个表达式,它是一个常量表达式。

对于foo(x),因为x引用类型的id-expression,受此规则约束:

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;

x 的生命周期在 foo(x) 的计算之前开始,并且它没有用常量表达式初始化,因为 std::string a; 不是左值常量表达式。如果把std::string a;修改成static std::string a;就可以了