在编译时评估向量的构建器模式(使用 `consteval`)

Builder patterns with vectors evaluated at compile time (with `consteval`)

我正在尝试创建一个遵循构建器模式并在编译时完全运行的 class(使用 C++20 中的新 consteval 关键字),但无论如何我试了不行。例如,这将不起作用:

#include <vector>

class MyClass
{
private:
    std::vector<int> data;

public:
    consteval MyClass& setData()
    {
        this->data = {20};
        return *this;
    }

    consteval std::vector<int> build()
    {
        return data;
    }
};


int main()
{
    std::vector<int> data = MyClass().setData().build();
}

给出错误“<anonymous> 不是常量表达式”。这让我相信我应该改为 return 份 class 的副本:

#include <vector>

class MyClass
{
private:
    std::vector<int> data;

public:
    consteval MyClass setData()
    {
        // https://herbsutter.com/2013/04/05/complex-initialization-for-a-const-variable/
        return [&]{
            MyClass newClass;
            newClass.data = {20};
            return newClass;
        }();
    }

    consteval std::vector<int> build()
    {
        return data;
    }
};


int main()
{
    std::vector<int> data = MyClass().setData().build();
}

然而,我得到了同样的错误。我应该如何在 C++ 中使用恒定时间构建器模式?这似乎只发生在 vectors 上,我使用的是支持 C++20 constexpr vectors 的版本。

您的代码无法编译,因为当前的 C++ 只允许在常量表达式中进行“瞬态”分配。这意味着在常量表达式计算期间,允许动态分配内存 (C++20 起),但前提是任何此类分配在常量表达式“结束”时被释放。

在您的代码中,表达式 MyClass().setData() 必须是常量表达式,因为它是一个 立即调用 (这意味着调用 consteval 函数,除了出现在另一个 consteval 函数内或 if consteval 块内的)。表达式 MyClass().setData().build() 也必须是常量表达式。这意味着,在评估 MyClass().setData().build() 时,允许动态分配,但在 MyClass().setData() 末尾和 MyClass().setData().build() 末尾都不能有“幸存”分配。

由于无法阻止 MyClass().setData() 的结果进行实时分配,因此您只能在封闭的 consteval 函数或 if consteval 块中调用它。例如,以下将是有效的:

consteval int foo() {
    return MyClass().setData().build()[0];
}

请注意,临时 MyClass 对象(以及 std::vector<int> 子对象)将被销毁,因此所有动态分配都将被清除,就在 foo() [=39 之前=].

您想在最外层的 consteval 函数完成后保留向量吗?抱歉,您不能那样做——至少在当前版本的 C++ 中不能。您需要将其内容复制到 std::array 或其他不使用动态分配的对象中。

<source>: In function 'int main()':
<source>:24:46: error: '<anonymous>' is not a constant expression
   24 |     std::vector<int> data = MyClass().setData().build();
      |                             ~~~~~~~~~~~~~~~~~^~

注意编译器如何取消行 MyClass().setData()

setData() returns 对 *this 的引用,其类型为 MyClass 而不是 const。您实际上只是在 setData() 中修改了它。对 non-const 的引用不能是常量表达式。

您可以通过按值返回来更改它:consteval MyClass setData()

但是你 运行 遇到了一个问题,即在 constexpr 中,所有对 new 的调用必须与对 delete 和 elided 的调用保持平衡。但是 std::vector 调用 new 并且您永远不会破坏向量。所以你得到:

/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/allocator.h:182:50: error: 'MyClass::setData()()' is not a constant expression because it refers to a result of 'operator new'

如果你使用std::array,你可以做到consteval。或者其他一些不使用堆的容器。