【问题标题】:Why references can't be used with compile time functions?为什么引用不能与编译时函数一起使用?
【发布时间】:2018-12-29 14:27:19
【问题描述】:

我有两个 sn-ps。

第一个sn-p:

#include <string>

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

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

第二个sn-p:

#include <string>

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

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

第一个编译,第二个不编译(错误信息:错误:'x'的值在常量表达式中不可用。为什么?为什么a可以使用在常量表达式中,x 不是?

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

【问题讨论】:

    标签: c++ c++17 constexpr compile-time constant-expression


    【解决方案1】:

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

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

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

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


    标准中的相关部分(无关部分被我省略了):

    [expr.const]/2:

    表达式 e 是一个核心常量表达式,除非根据抽象机的规则对 e 的求值将求值以下表达式之一:

    • ...

    • 一个 id 表达式,它引用引用类型的变量或数据成员,除非该引用具有前面的初始化并且要么

      • 用常量表达式初始化或

      • 它的生命周期始于对 e 的评估;

    • ...

    [expr.const]/6:

    常量表达式是一个泛左值核心常量表达式,它引用一个实体,该实体是常量表达式(如下定义)的允许结果,... 如果实体是具有静态存储持续时间的对象,该对象要么不是临时对象,要么是其值满足上述条件的临时对象,则该实体是常量表达式的允许结果约束,或者它是一个函数。

    【讨论】:

    • @jackX 是的,它有时可能被认为是一个完整的表达式,但它不是一个表达式。
    • 表示对象名称的 id 表达式不是泛左值吗?
    • @jackX 这是一个左值。
    • 是的。所以,a 在问题中,它的评估也决定了一个对象的身份。
    【解决方案2】:

    因为您在编译时无法知道x 的值是多少,所以您只知道它将指向a。您正在做的是检查x 的“值”,但x 的值是a 所在的地址,您不知道a 将被分配到哪里,地址也不是恒定的。

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

    这个问题包含更多细节:how to initialize a constexpr reference

    【讨论】:

    • 那为什么第一个 sn-p 有效?函数通过引用而不是值来获取它的参数。函数不能在编译时按值获取std::string,因为std::string 不是编译时类型。
    【解决方案3】:

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

    当引用类型的参数直接绑定到参数表达式时,隐式转换序列是身份转换,除非参数表达式的类型是参数类型的派生类,在在这种情况下,隐式转换序列是派生到基础的转换

    由于
    [over.ics.scs#2]

    ,它什么也没做

    如条款 [conv] 中所述,标准转换序列要么是 Identity 转换本身(即无转换

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

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

    引用引用类型的变量或数据成员的 id 表达式,除非该引用具有前面的初始化并且

    • 用常量表达式初始化或
    • 它的生命周期始于对 e 的评估;

    x 的生命周期在 foo(x) 的评估之前开始,它没有使用常量表达式初始化,因为 std::string a; 不是泛左值常量表达式。把std::string a;修改成static std::string a;就OK了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-13
      • 2017-07-03
      • 2022-01-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多