C++ 自定义类型转换运算符不使用';在转发引用上调用时不起作用(通过值传递对象时起作用)

C++ 自定义类型转换运算符不使用';在转发引用上调用时不起作用(通过值传递对象时起作用),c++,c++17,constexpr,rvalue-reference,pass-by-rvalue-reference,C++,C++17,Constexpr,Rvalue Reference,Pass By Rvalue Reference,我无法理解这个错误的本质,所以如果标题能更好的话,请原谅我。此代码无法编译: 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

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

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());
};

<代码>模板

这里有一个更短的复制,考虑编译的程序与<代码> Access < /C> >和一个没有

的程序之间的区别。
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{});
只有一个函数
foo
,因此它必须有一个特定的返回类型。如果这个程序被允许,那么
foo(Int{1})
将返回一个
X
foo(Int{2})
将返回一个
X
——也就是说,
foo
可以返回不同的类型?这不可能发生,所以这必须是不正确的

最小的盒子 当我们处于一个需要一个常量表达式的情况下,可以将其视为打开一个新的盒子。该框中的所有内容都必须满足常量求值的规则,就好像我们刚刚从那个点开始一样。如果需要嵌套在该框中的新常量表达式,则打开一个新框。一路下来的箱子

在原始复制品(使用
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{});
因为每次进入常量计算时,我们仍然只有一个入口,在该计算中,引用
t
具有前面的初始化,并且
Int
的成员也可以在常量表达式中使用

回到手术室 删除引用是有效的,因为我们不再违反引用限制,并且没有任何其他可以违反的限制。我们没有读取任何变量状态或任何东西,转换函数只是返回一个常量


类似的示例中,我们试图通过值将
Int{1}
传递给
foo
,但仍然会失败—这次不是针对引用规则,而是针对左值到右值的转换规则。基本上,我们正在读一些我们不允许读的东西——因为我们最终会遇到同样的矛盾,即能够构造一个具有多个返回类型的函数。

index
在lambda中不是编译时常量,你的整个
值作为类型对我来说毫无意义,由于它将一个数字作为模板参数,并将其转换为它已经是的相同类型,那么调用
f(value_as_type{})之间的区别是什么和简单地
f(第一)?@RemyLebeau:这并不能解释为什么它在没有
&&
的情况下工作@RemyLebeau:如果所有其他方法都失败了,我可以使用
decltype(index)::value
,这就是意义所在。但是我不明白为什么转换操作符不能工作。也许在这种特殊情况下,你建议的简化会起作用,我会尝试一下(在C++17之前,它肯定不起作用)。@RemyLebeau它不一定是
index.constexpr操作符type()
不依赖于
index
变量的编译时恒定性。非常有趣,非常感谢您的详细而巧妙的解释。我理解为什么多个返回类型是不正确的,但我仍然难以理解为什么完全编译时上下文中的引用是不正确的,尤其是为什么它可能是正确的,也可能不是正确的,这取决于外部代码。那么基本上,你是说编译器无法通过嵌套的“框”来分析和链接编译时调用吗?@VioletGiraffe在我看来,这是语言中最复杂的部分需要理解。@VioletGiraffe我认为说编译器不能是不对的。相反,我认为不可能支持。对某个在其生命周期内类型不正确的对象调用非静态成员函数是未定义的行为。常量求值必须捕获所有未定义的行为。因此,除非引用对象是已知的(即“具有先前的初始化”),否则不能执行这样的调用。函数的良好形式不能取决于调用它的方式。
struct One { constexpr operator int() const { return 1; } };

template <typename T>
constexpr int foo(T&& t) {
    return t;
}

constexpr int i = foo(One{});
constexpr int j = foo(Int{1});
constexpr int k = foo(Int{2});
static_assert(i+k == 3);