我无法理解此错误的实质,因此,如果标题更好,请原谅。该代码无法编译:
template <auto v>
struct value_as_type {
using type = decltype(v);
static constexpr type value {v};
constexpr operator type() const {
return v;
}
};
template <int First, int Last, typename Functor>
constexpr void static_for([[maybe_unused]] Functor&& f)
{
if constexpr (First < Last)
{
f(value_as_type<First>{});
static_for<First + 1, Last, Functor>(std::forward<Functor>(f));
}
}
template <class... FieldsSequence>
struct DbRecord
{
private:
static constexpr bool checkAssertions()
{
static_assert(sizeof...(FieldsSequence) > 0);
static_for<1, sizeof...(FieldsSequence)>([](auto&& index) {
constexpr int i = index;
static_assert(i > 0 && i < sizeof...(FieldsSequence));
});
return true;
}
private:
static_assert(checkAssertions());
};
错误行是constexpr int i = index;
,错误是“表达式未求常数”。
这是为什么?我期望value_as_type<int>
对象的转换运算符被调用。最令人困惑的是,如果lambda使用auto index
而不是auto&& index
,它确实可以正常工作。
答案 0 :(得分:2)
这里的复制时间较短,请考虑使用ACCEPT
编译的程序与不使用以下程序的程序之间的区别:
struct One { constexpr operator int() const { return 1; } };
template <typename T>
constexpr int foo(T&& t) {
#ifdef ACCEPT
return t;
#else
constexpr int i = t;
return i;
#endif
}
constexpr int i = foo(One{});
正如我对宏的选择所暗示的那样,ACCEPT
情况还可以,而另一种情况是不正确的。为什么?有问题的规则是[expr.const]/4.12:
表达式
e
是核心常量表达式,除非遵循抽象机规则对e
的求值将对以下之一进行求值:[.. 。] id-expression 引用引用类型的变量或数据成员,除非引用具有先前的初始化且[...]
什么是之前的初始化?在我回答之前,lemme提供了一个不同的程序并逐步说明了它的语义:
struct Int { constexpr operator int() const { return i; } int i; };
template <int> struct X { };
template <typename T>
constexpr auto foo(T&& t) {
constexpr int i = t;
return X<i>{};
}
constexpr auto i = foo(Int{1});
constexpr auto j = foo(Int{2});
只有一个函数foo<Int>
,因此它必须具有一种特定的返回类型。如果允许该程序,那么foo(Int{1})
将返回X<1>
,而foo(Int{2})
将返回X<2>
,即foo<Int>
是否可以返回不同的类型?这不可能发生,因此必须格式错误。
当我们需要一个常量表达式时,可以将其视为打开一个新盒子。该框中的所有内容都必须满足不断评估的规则,就好像我们只是从这一点开始一样。如果我们需要在该框中嵌套一个新的常量表达式,请打开一个新框。箱子一直向下。
在原始复制(带有One
)和新复制(带有Int
)中,我们都有以下声明:
constexpr int i = t;
这将打开一个新框。初始化程序t
必须满足常量表达式的限制。 t
是引用类型,但在此框内没有先前的初始化 ,因此格式不正确。
现在在接受的情况下:
struct One { constexpr operator int() const { return 1; } };
template <typename T>
constexpr int foo(T&& t) {
return t;
}
constexpr int i = foo(One{});
我们只有一个框:全局i
的初始化。在该框中,我们仍会在该return t;
中评估引用类型的 id-expression ,但是在这种情况下,我们 do 在我们的内部进行了初始化框:我们可以看到将t
绑定到One{}
的位置。所以这可行。这些规则不能构成任何矛盾。确实,这也很好:
constexpr int j = foo(Int{1});
constexpr int k = foo(Int{2});
static_assert(i+k == 3);
因为我们每次仍然只有一个入口可以进行常量评估,并且在该评估中,引用t
具有预先的初始化,并且Int
的成员也可以在常量表达式中使用。
删除参考有效,因为我们不再违反参考限制,并且没有其他可以违反的限制。我们没有读取任何变量状态或任何内容,转换函数只是返回一个常量。
我们尝试通过值将Int{1}
传递到foo
的类似示例仍然会失败-这次不是参考规则,而是左值到右值转换规则。基本上,我们正在读一些我们不允许阅读的东西,因为我们最终陷入了能够构造具有多个返回类型的函数的矛盾。