自定义类型转换运算符在转发引用上调用时不起作用(当对象按值传递时有效)
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;
,错误是"表达式未计算为常数"。
这是为什么呢?我希望调用value_as_type<int>
对象的转换运算符。最令人困惑的是,如果 lambda 采用auto index
而不是auto&& index
,它确实可以正常工作。
在线演示:https://godbolt.org/z/TffIIn
这是一个较短的复制品,考虑使用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表达式,除非引用具有前面的初始化并且[...]
什么是前面的初始化?在我回答这个问题之前,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 表达式,但在这种情况下,我们的框中确实有一个预先的初始化:我们可以看到将t
绑定到One{}
的位置。所以这行得通。从这些规则中可以构建任何矛盾。事实上,这也很好:
constexpr int j = foo(Int{1});
constexpr int k = foo(Int{2});
static_assert(i+k == 3);
因为我们每次仍然只有一个入口进入常量求值,并且在该求值中,引用t
具有先前的初始化,并且Int
的成员也可以在常量表达式中使用。
返回OP
删除参考文献是有效的,因为我们不再违反参考限制,并且没有任何其他我们可以违反的限制。我们不读取任何变量状态或任何东西,转换函数只是返回一个常量。
我们尝试按值传递Int{1}
foo
的类似示例仍然会失败 - 这次不是参考规则,而是左值到右值转换规则。基本上,我们正在阅读一些我们不能被允许阅读的东西 - 因为我们最终会遇到同样的矛盾,即能够构造具有多个返回类型的函数。
- 我正在开发服务器,ip作为参数传递不起作用
- 何时应通过引用传递矢量参数而不是按值传递矢量参数?
- if 子句中的赋值不起作用
- 通过构造函数创建一些值并尝试添加到文档中使用 rapidjson 不起作用
- 使用 va_arg 传递给函数va_list不起作用
- 不同于按值传递和常量引用传递的程序集
- 我正在尝试解决一个需要数组总和值但代码不起作用的问题,我想做这样的事情
- Cuda 基本程序 (将值写入矩阵和 std:cout 不起作用) ;主功能不启动
- 在 C++ 中通过引用传递类成员时,Const 不起作用
- 用于 AES-gcm 加密的 IV 中是否有不起作用的值?
- C++:传递临时值不起作用,如何解决?
- 为什么在按值返回时创建临时对象,而不是在按值传递给函数参数时创建临时对象
- 为什么以下成员初始值设定项在c++中不起作用
- 将复制构造函数设置为默认值在继承自 QObject 时不起作用
- 将数组作为引用传递到函数中不起作用
- 我正在努力在随机数组中查找最小值,有人知道为什么我的代码不起作用?C++
- 自定义类型转换运算符在转发引用上调用时不起作用(当对象按值传递时有效)
- C++:强制实施函数,使其不能接受按值传递的任何参数
- 使用返回值传递到构造函数不起作用
- 为什么 GCC 的 std::function 不使用对按值传递的参数的右值引用来在其内部委托之间传递它们?