在转发引用上调用自定义类型转换运算符时不起作用(在按值传递 object 时起作用)
Custom type conversion operator doesn't work when invoked on a forwarding reference (works when object is passed by value)
我无法理解这个错误的本质,如果标题可以更好,请原谅。此代码无法编译:
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;
,错误为"expression did not evaluate to a constant"。
这是为什么?我希望调用 value_as_type<int>
object 的转换运算符。最令人困惑的是,如果 lambda 采用 auto index
而不是 auto&& index
.
,它确实可以正常工作
这是一个较短的复制,考虑用 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:
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: [...] an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either [...]
什么是前置初始化?在我回答这个问题之前,让我提供一个不同的程序并了解它的语义:
矛盾
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>
,因此它必须具有一种特定的 return 类型。如果允许此程序,则 foo(Int{1})
将 return 和 X<1>
,而 foo(Int{2})
将 return 和 X<2>
-- 也就是说,foo<Int>
可以return 不同类型吗?这不可能发生,所以这必须是错误的。
尽可能小的盒子
当我们处于需要常量表达式的情况时,可以将其视为打开一个新盒子。该框中的所有内容都必须满足不断评估的规则,就好像我们刚刚从那一点开始一样。如果我们需要在该框中嵌套一个新的常量表达式,我们会打开一个新框。盒子一直往下。
在原始复制品(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
的成员也可以在常量表达式中使用.
回到OP
删除参考作品是因为我们不再违反参考限制,并且没有任何其他限制可以违反。我们没有读取任何变量状态或任何东西,转换函数只是 returns 一个常量。
我们尝试按值将 Int{1}
传递给 foo
的类似示例仍然会失败 - 这次不是引用规则,而是左值到右值转换规则。基本上,我们正在阅读一些我们不能被允许阅读的东西——因为我们最终会遇到同样的矛盾,即能够构造具有多个 return 类型的函数。
我无法理解这个错误的本质,如果标题可以更好,请原谅。此代码无法编译:
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;
,错误为"expression did not evaluate to a constant"。
这是为什么?我希望调用 value_as_type<int>
object 的转换运算符。最令人困惑的是,如果 lambda 采用 auto index
而不是 auto&& index
.
这是一个较短的复制,考虑用 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:
An expression
e
is a core constant expression unless the evaluation ofe
, following the rules of the abstract machine, would evaluate one of the following: [...] an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either [...]
什么是前置初始化?在我回答这个问题之前,让我提供一个不同的程序并了解它的语义:
矛盾
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>
,因此它必须具有一种特定的 return 类型。如果允许此程序,则 foo(Int{1})
将 return 和 X<1>
,而 foo(Int{2})
将 return 和 X<2>
-- 也就是说,foo<Int>
可以return 不同类型吗?这不可能发生,所以这必须是错误的。
尽可能小的盒子
当我们处于需要常量表达式的情况时,可以将其视为打开一个新盒子。该框中的所有内容都必须满足不断评估的规则,就好像我们刚刚从那一点开始一样。如果我们需要在该框中嵌套一个新的常量表达式,我们会打开一个新框。盒子一直往下。
在原始复制品(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
的成员也可以在常量表达式中使用.
回到OP
删除参考作品是因为我们不再违反参考限制,并且没有任何其他限制可以违反。我们没有读取任何变量状态或任何东西,转换函数只是 returns 一个常量。
我们尝试按值将 Int{1}
传递给 foo
的类似示例仍然会失败 - 这次不是引用规则,而是左值到右值转换规则。基本上,我们正在阅读一些我们不能被允许阅读的东西——因为我们最终会遇到同样的矛盾,即能够构造具有多个 return 类型的函数。