为什么 constexpr 函数中的引用参数不是常量表达式?

问题描述 投票:0回答:3

考虑以下函数:

template <size_t S1, size_t S2>
auto concatenate(const std::array<uint8_t, S1> &data1,
                 const std::array<uint8_t, S2> &data2)
{
    std::array<uint8_t, data1.size() + data2.size()> result; // <<< error here

    auto iter = std::copy(data1.begin(), data1.end(), result.begin());
    std::copy(data2.begin(), data2.end(), iter);

    return result;
}

int main()
{
    std::array<uint8_t, 1> data1{ 0x00 };
    std::array<uint8_t, 1> data2{ 0xFF };

    auto result = concatenate(data1, data2);
    return 0;
}

当使用 clang 6.0 和 -std=c++17 编译时,该函数无法编译,因为数组上的 size 成员函数不是 constexpr,因为它是引用。错误信息是这样的:

错误:非类型模板参数不是常量表达式

当参数是不是引用时,代码将按预期工作。

我想知道为什么会这样,因为 size() 实际上返回一个模板参数,它几乎不能再 const 了。参数是否是引用应该没有什么区别。

我知道我当然可以使用 S1 和 S2 模板参数,该函数只是问题的简短说明。

标准中有什么内容吗?我非常惊讶地发现编译错误。

c++ language-lawyer constexpr constant-expression
3个回答
11
投票

因为你评估了一个参考。来自 [expr.const]/4:

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

  • ...
  • 一个id-表达式,引用引用类型的变量或数据成员,除非该引用有一个预先的初始化并且
    • 它可用于常量表达式或
    • 其生命周期开始于 e 的评估内;
  • ...

您的引用参数没有预先初始化,因此不能在常量表达式中使用。

您可以在这里简单地使用

S1 + S2
来代替。


7
投票

针对 clang 的此问题报告了一个错误,标题为:Clang 不允许在非类型模板参数中使用 constexpr 类型转换

其中的讨论表明这并不是一个真正的错误。

表达式 e 是核心常量表达式,除非计算 e,遵循抽象机的规则,将评估以下之一 以下表达式:

  • [...]
  • 引用引用类型的变量或数据成员的 id 表达式,除非该引用具有预先的初始化并且 任何一个
    • 它用常量表达式初始化或
    • 其生命周期开始于 e 的评估内;
  • [...]

以上引用来自n4659草案的[expr.const]/2.11,并添加了强调。


0
投票

不幸的是,标准规定在类成员访问表达式中计算点或箭头之前的后缀表达式;63[expr.ref]/1。后缀表达式是

a
中的
a.b
。这篇笔记真的很有趣,因为这正是这里的情况:

63) 如果对类成员访问表达式进行求值,则即使结果不需要确定整个后缀表达式的值,也会发生子表达式求值,例如,如果 id 表达式表示静态成员。

因此,即使没有必要,也会对

data
进行求值,并且常量表达式前的规则也适用于它。

© www.soinside.com 2019 - 2024. All rights reserved.