5

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

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

4

1 回答 1

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 表达式,它引用引用类型的变量或数据成员,除非引用具有前面的初始化,并且 [...]

什么是前面的初始化?在我回答这个问题之前,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>可以返回不同的类型吗?这不可能发生,所以这必须是不正确的。

最小的盒子

当我们处于需要不断表达的情况时,将其视为打开一个新盒子。该框中的所有内容都必须满足不断评估的规则,就好像我们刚刚从那个点开始一样。如果我们需要一个嵌套在该框内的新常量表达式,我们将打开一个新框。盒子一直向下。

在原始复制品 (with One) 和新复制品 (with Int) 中,我们都有这样的声明:

constexpr int i = t;

这将打开一个新框。初始化器t必须满足常量表达式的限制。是一个引用类型,但在此 box 中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;,在里面,但是在这种情况下,我们盒子里有一个前面的初始化:我们在绑定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会失败 - 这次不是针对引用规则,而是针对左值到右值的转换规则。基本上,我们正在阅读一些我们不能被允许阅读的东西——因为我们最终会遇到同样的矛盾,即能够构造一个具有多种返回类型的函数。

于 2019-09-27T16:08:47.063 回答