gpt4 book ai didi

c++ - 在转发引用上调用时自定义类型转换运算符不起作用(当对象按值传递时起作用)

转载 作者:行者123 更新时间:2023-12-02 10:59:24 24 4
gpt4 key购买 nike

我无法理解这个错误的本质,所以如果标题可以更好,请原谅我。此代码无法编译:

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 :

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> ,因此它必须具有一种特定的返回类型。如果允许该程序,则 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的初始化。在该框中,我们仍然评估引用类型的id-表达式,即 return t; ,但在这种情况下,我们确实在我们的框中进行了预先的初始化:我们在绑定(bind) 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 by value 仍然会失败 - 这次不是针对引用规则,而是针对左值到右值转换规则。基本上,我们正在阅读一些我们不被允许阅读的内容——因为我们最终会遇到与能够构造具有多个返回类型的函数相同的矛盾。

关于c++ - 在转发引用上调用时自定义类型转换运算符不起作用(当对象按值传递时起作用),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58125039/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com