gpt4 book ai didi

C++ 20:std::array 作为非类型模板参数重新排列元素

转载 作者:行者123 更新时间:2023-12-04 01:05:49 25 4
gpt4 key购买 nike

我最近实现了一个 Builder 类,但我想避免抛出异常。所以我有一个想法,我可以用一个 bool 数组来参数化 Builder,这些 bool 表示已经设置了哪些字段。每个 setter 将返回 Builder 的新特化,并设置了相应的字段标志。这样我就可以检查在编译时是否设置了正确的字段。

事实证明,作为非类型模板参数的复杂数据类型仅在 C++ 20 中可用。但我还是进行了试验。

事实证明它以一种奇怪的方式行为不端。随着每个新的特化返回,“真”标志在开始时聚集在一起,如示例调试输出所示:

 - set field 4 old flags 00000 new flags 00001
- set field 2 old flags 10000 new flags 10100
- set field 0 old flags 11000 new flags 11000
- set field 3 old flags 11000 new flags 11010
- set field 1 old flags 11100 new flags 11100

这些来自下面两行中的第二行。删除第一个可以解决问题,这表明第一个实例化以某种方式影响了第二个。

Fields fields1 = Builder().SetFirst(1).SetSecond(2).SetThird(3).SetFourth(4).SetFifth(5).Build();
Fields fields2 = Builder().SetFifth(5).SetThird(3).SetFirst(1).SetFourth(4).SetSecond(2).Build();

它应该这样做吗?这只是我以某种方式遗漏的 C++ 20 的微妙之处,还是 gcc 中的错误?

我用 gcc 9.3.0 和 gcc 10.2.0 检查了这个。我也尝试从 git 编译,版本 11.0.1 更改为 a18ebd6c439。命令行是 g++ -Wall --std=c++2a builder.cpp。他们都以同样的方式行事。我还在 gcc 的 bugzilla 中进行了搜索,但找不到任何看起来相似的内容。

下面是两个代码示例。首先,我尽可能地剥离了一个版本来显示问题。第二个显示了我试图实现的目标的更多背景信息。 (还有第三个更现实的版本,但公开发布可能会有问题。)

#include <array>
#include <cassert>

using Flags = std::array<bool, 2>;

template<Flags flags = Flags{}>
class Builder
{
public:
Builder() {
}

auto SetFirst() {
constexpr auto new_flags = SetFieldFlag<0>();
Builder<new_flags> new_builder;
return new_builder;
}

auto SetSecond() {
constexpr auto new_flags = SetFieldFlag<1>();
Builder<new_flags> new_builder;
return new_builder;
}

Flags GetFlags() const {
return flags;
}

private:
template<int field>
static constexpr auto SetFieldFlag() {
auto new_flags = flags;
std::get<field>(new_flags) = true;
return new_flags;
}
};

int main()
{
auto flags1 = Builder().SetFirst().SetSecond().GetFlags();
assert(flags1[0]);
assert(flags1[1]);

auto flags2 = Builder().SetSecond().SetFirst().GetFlags();
assert(flags2[0]);
assert(flags2[1]);

return 0;
}
#include <iostream>
#include <array>

constexpr int NumFields = 5;
using Flags = std::array<bool, NumFields>;
using Fields = std::array<int, NumFields>;

std::ostream& operator<<(std::ostream& out, Flags flags) {
for (int i = 0; i < NumFields; ++i) {
out << flags[i];
}
return out;
}

std::ostream& operator<<(std::ostream& out, Fields fields) {
for (int i = 0; i < NumFields; ++i) {
out << (i ? ":" : "") << fields[i];
}
return out;
}

template<Flags flags = Flags{}>
class Builder
{
public:
Builder(Fields fields_in = Fields{})
: fields(fields_in) {
}

auto SetFirst(int value) {
fields.at(0) = value;
return BuilderWithField<0>();
}

auto SetSecond(int value) {
fields.at(1) = value;
return BuilderWithField<1>();
}

auto SetThird(int value) {
fields.at(2) = value;
return BuilderWithField<2>();
}

auto SetFourth(int value) {
fields.at(3) = value;
return BuilderWithField<3>();
}

auto SetFifth(int value) {
fields.at(4) = value;
return BuilderWithField<4>();
}

Fields Build() {
std::cout << " - build with flags " << flags << std::endl;
static_assert(std::get<0>(flags), "first field not set");
static_assert(std::get<1>(flags), "second field not set");
static_assert(std::get<2>(flags), "third field not set");
static_assert(std::get<3>(flags), "fourth field not set");
static_assert(std::get<4>(flags), "fifth field not set");
return fields;
}

private:
template<int field>
static constexpr auto SetFieldFlag() {
auto new_flags = flags;
std::get<field>(new_flags) = true;
return new_flags;
}

template<int field>
auto BuilderWithField() {
constexpr auto new_flags = SetFieldFlag<field>();
std::cout << " - set field " << field << " old flags " << flags << " new flags " << new_flags << std::endl;
Builder<new_flags> new_builder(fields);
return new_builder;
}

Fields fields;
};

int main()
{
Fields fields1 = Builder().SetFirst(1).SetSecond(2).SetThird(3).SetFourth(4).SetFifth(5).Build();
std::cout << fields1 << std::endl;

Fields fields2 = Builder().SetFifth(5).SetThird(3).SetFirst(1).SetFourth(4).SetSecond(2).Build();
std::cout << fields2 << std::endl;

return 0;
}

最佳答案

我用过https://godbolt.org/检查为多个编译器生成的代码,这确实是 gcc 中的一个错误。 clang 和 msvc 都会产生正确的结果。

这是有趣的部分,为方法 Builder<std::array<bool, 2ul>{}>::SetSecond() 生成的汇编程序这会导致您较短的示例中出现错误。实际代码并不那么重要,错误可以通过查看类型看出:

Clang produces (correctly) this:

Builder<std::array<bool, 2ul>{}>::SetSecond(): # @Builder<std::array<bool, 2ul>{}>::SetSecond()
push rbp
mov rbp, rsp
sub rsp, 32
mov qword ptr [rbp - 8], rdi
mov ax, word ptr [.L__const.Builder<std::array<bool, 2ul>{}>::SetSecond().new_flags]
mov word ptr [rbp - 16], ax
lea rdi, [rbp - 24]
call Builder<std::array<bool, 2ul>{bool [2]{false, true}}>::Builder() [base object constructor]
add rsp, 32
pop rbp
ret

GCC produces (incorrectly) this:

Builder<std::array<bool, 2ul>{}>::SetSecond():
push rbp
mov rbp, rsp
push rbx
sub rsp, 40
mov QWORD PTR [rbp-40], rdi
mov WORD PTR [rbp-18], 0
mov BYTE PTR [rbp-17], 1
lea rax, [rbp-19]
mov rdi, rax
call Builder<std::array<bool, 2ul>{bool [2]{true}}>::Builder() [complete object constructor]
nop
mov eax, ebx
mov rbx, QWORD PTR [rbp-8]
leave
ret

如果比较得到call的函数类型ed,你可以清楚地看到在gcc中,SetSecond()没有设置第二个——有 {true} , 但应该是 {false, true} .

那么,是时候切换到 clang 了吗?

关于C++ 20:std::array 作为非类型模板参数重新排列元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66520012/

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