考虑以下函数:
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 模板参数,该函数只是问题的简短说明。
标准中有什么内容吗?我非常惊讶地发现编译错误。
因为你评估了一个参考。来自 [expr.const]/4:
表达式 e 是一个核心常量表达式,除非对 e 的求值遵循抽象机的规则,将求值以下表达式之一:
- ...
- 一个id-表达式,引用引用类型的变量或数据成员,除非该引用有一个预先的初始化并且
- 它可用于常量表达式或
- 其生命周期开始于 e 的评估内;
- ...
您的引用参数没有预先初始化,因此不能在常量表达式中使用。
您可以在这里简单地使用
S1 + S2
来代替。
针对 clang 的此问题报告了一个错误,标题为:Clang 不允许在非类型模板参数中使用 constexpr 类型转换。
其中的讨论表明这并不是一个真正的错误。
表达式 e 是核心常量表达式,除非计算 e,遵循抽象机的规则,将评估以下之一 以下表达式:
- [...]
- 引用引用类型的变量或数据成员的 id 表达式,除非该引用具有预先的初始化并且 任何一个
- 它用常量表达式初始化或
- 其生命周期开始于 e 的评估内;
- [...]
以上引用来自n4659草案的[expr.const]/2.11,并添加了强调。
不幸的是,标准规定在类成员访问表达式中计算点或箭头之前的后缀表达式;63[expr.ref]/1。后缀表达式是
a
中的 a.b
。这篇笔记真的很有趣,因为这正是这里的情况:
63) 如果对类成员访问表达式进行求值,则即使结果不需要确定整个后缀表达式的值,也会发生子表达式求值,例如,如果 id 表达式表示静态成员。
因此,即使没有必要,也会对
data
进行求值,并且常量表达式前的规则也适用于它。