簡體   English   中英

C++ 20:std::array 作為非類型模板參數重新洗牌元素

[英]C++ 20: std::array as non-type template argument reshuffles elements

我最近實現了一個 Builder class,但我想避免拋出異常。 所以我有一個想法,我可以用一組表示已設置哪些字段的布爾值對 Builder 進行參數化。 每個 setter 都將返回 Builder 的新特化,並設置了相應的字段標志。 這樣我就可以檢查在編譯時是否設置了正確的字段。

事實證明,作為非類型模板 arguments 的復雜數據類型僅在 C++ 20 中可用。但我還是嘗試了它。

事實證明,它以一種奇怪的方式行為不端。 隨着每個新的專業化返回,“真”標志在開始時聚集在一起,如此示例調試 output 所示:

 - 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 產生(正確)這個:

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 產生(錯誤地)這個:

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的 function 的類型,您可以清楚地看到在 gcc 中, SetSecond()沒有設置第二個 - 有{true} ,但應該是{false, true}

那么,是時候切換到 clang 了嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM