代码之家  ›  专栏  ›  技术社区  ›  Guss

P1061“结构化绑定可以引入包”的任何C++20替代方案/技巧?

  •  1
  • Guss  · 技术社区  · 4 年前

    是否有任何方法可以将结构化绑定用于任意数量的标识?

    P1061 "Structured Bindings can introduce a Pack" 提供了一个方便的解决方案,但尚未提供。

    我想要实现的是为聚合类型提供一个类似元组的接口。
    ( std::get<N>(T) ,则, std::tuple_element_t<T> 等)。

    我已经有了一个计算字段的函数,所以我现在要寻找的是一种实现以下功能的方法,甚至是一种棘手的方法:

    template <std::size_t N>
    constexpr auto as_tuple(auto && value) noexcept {
        auto & [ /* N identities ...*/ ] = value;
        return std::tuple/* of references */{ /* N identities... */ };
    }
    

    满怀绝望,我尝试了一些使用预处理器的想法 (免责声明:不是我喜欢的) ,没有可扩展的结果。

    // 8 bits [0..255]
    #define PP_IDENTITY_BIT_1(...)   id_##__VA_ARGS__
    #define PP_IDENTITY_BIT_2(...)   PP_IDENTITY_BIT_1(__VA_ARGS__ ## 0), PP_IDENTITY_BIT_1(__VA_ARGS__ ## 1)
    #define PP_IDENTITY_BIT_3(...)   PP_IDENTITY_BIT_2(__VA_ARGS__ ## 0), PP_IDENTITY_BIT_2(__VA_ARGS__ ## 1)
    #define PP_IDENTITY_BIT_4(...)   PP_IDENTITY_BIT_3(__VA_ARGS__ ## 0), PP_IDENTITY_BIT_3(__VA_ARGS__ ## 1)
    #define PP_IDENTITY_BIT_5(...)   PP_IDENTITY_BIT_4(__VA_ARGS__ ## 0), PP_IDENTITY_BIT_4(__VA_ARGS__ ## 1)
    #define PP_IDENTITY_BIT_6(...)   PP_IDENTITY_BIT_5(__VA_ARGS__ ## 0), PP_IDENTITY_BIT_5(__VA_ARGS__ ## 1)
    #define PP_IDENTITY_BIT_7(...)   PP_IDENTITY_BIT_6(__VA_ARGS__ ## 0), PP_IDENTITY_BIT_6(__VA_ARGS__ ## 1)
    #define PP_IDENTITY_BIT_8(...)   PP_IDENTITY_BIT_7(__VA_ARGS__ ## 0), PP_IDENTITY_BIT_7(__VA_ARGS__ ## 1)
    
    // key idea : conditionaly expand macros, based on bitwise check,
    // like `(N & 1) != 0` -> `PP_IDENTITY_BIT_1`
    
    

    操场: https://godbolt.org/z/nxaocqoKj

    0 回复  |  直到 4 年前
        1
  •  3
  •   Turtlefight    4 年前

    不幸的是,从C++20开始,结构化绑定不支持参数包。

    不过,您可以通过为每个不同数量的参数提供一个实现来解决这个问题(如您的示例中所示)


    1、使用 if constexpr

    通过使用 如果constexpr -如果条件不为true,则会丢弃整个语句(对于未实例化的模板化实体,也就是说,它不必为给定的模板类型进行编译)。
    auto 返回类型仅从未丢弃的返回语句推断返回类型,因此您可以使用 如果constexpr 更改函数的返回类型。

    这包括:

    8.5.2 (2) The if statement

    如果转换条件的值为false,则第一个子状态 是丢弃的语句 ,否则,第二个子语句(如果存在)是丢弃的语句。在封闭模板实体的实例化过程中,如果条件在实例化后不依赖于值,则丢弃的子状态(如果有) 未实例化

    9.2.9.6.1 (3) Placeholder type specifiers

    如果声明的函数返回类型包含占位符类型,则函数的返回类型将从 未丢弃 函数体中的返回语句(如果有)。

    这允许您编写 as_tuple 功能如下:

    示例: godbolt

    #include <tuple>
    #include <concepts>
    
    template<class T>
    concept aggregate = std::is_aggregate_v<T>;
    
    struct any_type {
        template<class T>
        operator T() {}
    };
    
    // Count the number of members in an aggregate by (ab-)using
    // aggregate initialization
    template<aggregate T>
    consteval std::size_t count_members(auto ...members) {
        if constexpr (!requires { T{ members... }; })
            return sizeof...(members) - 1;
        else
            return count_members<T>(members..., any_type{});
    }
    
    template<aggregate T>
    constexpr auto as_tuple(T const& data) {
        constexpr std::size_t fieldCount = count_members<T>();
    
        if constexpr(fieldCount == 0) {
            return std::tuple();
        } else if constexpr (fieldCount == 1) {
            auto& [m1] = data;
            return std::tuple(m1);
        } else if constexpr (fieldCount == 2) {
            auto& [m1, m2] = data;
            return std::tuple(m1, m2);
        } else if constexpr (fieldCount == 3) {
            auto& [m1, m2, m3] = data;
            return std::tuple(m1, m2, m3);
        } else if constexpr (fieldCount == 4) {
            auto& [m1, m2, m3, m4] = data;
            return std::tuple(m1, m2, m3, m4);
        } else {
            static_assert(fieldCount!=fieldCount, "Too many fields for as_tuple(...)! add more if statements!");
        }
    }
    
    int main() {
        struct toto{ int i; };
        constexpr toto t{42};
        constexpr auto tup = as_tuple(t);
    
        static_assert(std::same_as<const int, std::tuple_element_t<0, decltype(tup)>>);
        static_assert(42 == std::get<0>(tup));
    }
    

    那么我们需要做的就是生成 如果constexpr 分支最多可支持最大数量的成员。

    注: 如果需要,也可以更换 std::tuple(...) 具有 std::tie(...) 返回对原始成员的引用元组,而不是复制值的元组。( godbolt example )


    2、生成分支

    2.1使用 Boost Preprocessor

    Boost预处理器 提供了许多方便的宏,我们可以使用它们轻松地生成所需的分支。

    在本例中,我们只需要其中的两个宏:

    • BOOST_PP_ENUM_PARAMS 生成变量名称。
      示例: BOOST_PP_ENUM_PARAMS(3, foo) 将扩展到 foo0, foo1, foo2

    • BOOST_PP_REPEAT_FROM_TO 重复调用宏。
      示例: BOOST_PP_REPEAT_FROM_TO(1, 3, FOO, ~) 将扩展到 FOO(z,1,~) FOO(z,2,~) FOO(z,3,~)

    这允许我们生成多达255个 如果constexpr 只有几行代码:

    Boost PP示例: godbolt

    template<aggregate T>
    constexpr auto as_tuple(T& data) {
        constexpr std::size_t fieldCount = count_members<T>();
    
        if constexpr(fieldCount == 0) {
            return std::tuple();
        }
    
    #define AS_TUPLE_STMT(z, n, unused) \
        else if constexpr(fieldCount == n) { \
            auto& [ BOOST_PP_ENUM_PARAMS(n, m) ] = data; \
            return std::tuple( BOOST_PP_ENUM_PARAMS(n, m) ); \
        }
    
        BOOST_PP_REPEAT_FROM_TO(1, BOOST_PP_LIMIT_REPEAT, AS_TUPLE_STMT, ~)
    
    #undef AS_TUPLE_STMT
    
        else {
            static_assert(fieldCount!=fieldCount, "Too many fields for as_tuple(...)! add more if statements!");
        }
    }
    

    此版本可以将最多包含255个成员的聚合转换为元组。 如果你需要更多,你可以改变 BOOST_PP_LIMIT_MAG & BOOST_PP_LIMIT_REPEAT 到512或1024,这将允许您分别处理最多511或1023个成员的聚合。

    2.2纯C++宏(无boost)

    如果没有boost,您将不得不手动编写大量样板宏代码——不幸的是,这是无法避免的。

    此示例使用宏“递归”,使用 deferred expressions 和重复扫描 EXPAND 宏)

    C++20还添加了 __VA_OPT__ 宏,这使得使用它更容易。

    示例: godbolt

    
    #define NUMBER_SEQ \
            50,49,48,47,46,45,44,43,42,41, \
            40,39,38,37,36,35,34,33,32,31, \
            30,29,28,27,26,25,24,23,22,21, \
            20,19,18,17,16,15,14,13,12,11, \
            10, 9, 8, 7, 6, 5, 4, 3, 2, 1
    
    #define PARENS ()
    #define UNWRAP(...) __VA_ARGS__
    #define FIRST(el, ...) el
    
    #define EXPAND(...) EXPAND4(EXPAND4(EXPAND4(EXPAND4(__VA_ARGS__))))
    #define EXPAND4(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
    #define EXPAND3(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
    #define EXPAND2(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
    #define EXPAND1(...) __VA_ARGS__
    
    
    #define SEQ_MAP(macro, ...) __VA_OPT__(EXPAND(SEQ_MAP_HELPER(macro, __VA_ARGS__)))
    #define SEQ_MAP_HELPER(macro, el, ...) macro(el __VA_OPT__(, __VA_ARGS__)) __VA_OPT__(SEQ_MAP_HELPER_AGAIN PARENS (macro, __VA_ARGS__))
    #define SEQ_MAP_HELPER_AGAIN() SEQ_MAP_HELPER
    
    #define EXPANDZ(...) EXPANDZ4(EXPANDZ4(EXPANDZ4(EXPANDZ4(__VA_ARGS__))))
    #define EXPANDZ4(...) EXPANDZ3(EXPANDZ3(EXPANDZ3(EXPANDZ3(__VA_ARGS__))))
    #define EXPANDZ3(...) EXPANDZ2(EXPANDZ2(EXPANDZ2(EXPANDZ2(__VA_ARGS__))))
    #define EXPANDZ2(...) EXPANDZ1(EXPANDZ1(EXPANDZ1(EXPANDZ1(__VA_ARGS__))))
    #define EXPANDZ1(...) __VA_ARGS__
    
    #define SEQ_MAPZ(macro, ...) __VA_OPT__(EXPANDZ(SEQ_MAPZ_HELPER(macro, __VA_ARGS__)))
    #define SEQ_MAPZ_HELPER(macro, el, ...) macro(el __VA_OPT__(, __VA_ARGS__)) __VA_OPT__(, SEQ_MAPZ_HELPER_AGAIN PARENS (macro, __VA_ARGS__))
    #define SEQ_MAPZ_HELPER_AGAIN() SEQ_MAPZ_HELPER
    
    #define ADD_M(x, ...) m##x
    #define GEN_BRANCH(...) \
        else if constexpr(fieldCount == FIRST(__VA_ARGS__)) { \
            auto& [ SEQ_MAPZ(ADD_M, __VA_ARGS__) ] = data; \
            return std::tuple( SEQ_MAPZ(ADD_M, __VA_ARGS__) ); \
        }
    
    template<aggregate T>
    constexpr auto as_tuple(T& data) {
        constexpr std::size_t fieldCount = count_members<T>();
    
        if constexpr(fieldCount == 0) {
            return std::tuple();
        }
    
        SEQ_MAP(GEN_BRANCH, NUMBER_SEQ)
    
        else {
            static_assert(fieldCount!=fieldCount, "Too many fields for as_tuple(...)! add more if statements!");
        }
    }
    

    此版本最多支持50个聚合成员,但您可以通过向 NUMBER_SEQ 并添加更多 EXPANDx 宏(根据需要)(每个 EXPANDx公司 宏您添加的扫描数是原来的四倍,因此您只需要几个扫描)

    建议阅读:

    2.3编写代码生成器

    除了使用宏生成代码之外,您还可以编写一个程序,作为构建设置的一部分,为实际程序生成头。

    这使得代码更容易阅读(没有宏骗局),并且您只需为您想要支持的成员数生成一次代码。

    示例: godbolt

    #include <iostream>
    #include <vector>
    
    int main() {
        std::cout << R"(
    #include <tuple>
    #include <concepts>
    
    template<class T>
    concept aggregate = std::is_aggregate_v<T>;
    
    struct any_type {
        template<class T>
        operator T() {}
    };
    
    template<aggregate T>
    consteval std::size_t count_members(auto ...members) {
        if constexpr (!requires { T{ members... }; })
            return sizeof...(members) - 1;
        else
            return count_members<T>(members..., any_type{});
    }
    
    template<aggregate T>
    constexpr auto as_tuple(T const& data) {
        constexpr std::size_t fieldCount = count_members<T>();
    
        if constexpr(fieldCount == 0) {
            return std::tuple();
        }
    )";
    
        std::string variables;
        for(int i = 1; i <= 10; i++) {
            if(variables.length() > 0) variables += ", ";
            variables += "m" + std::to_string(i);
            
            std::cout
                << "    else if constexpr(fieldCount == " << i << ") {\n"
                << "        auto& [" << variables << "] = data;\n"
                << "        return std::tuple(" << variables << ");\n"
                << "    }\n";
        }
    
        std::cout
            << "    else {\n"
            << "        static_assert(fieldCount!=fieldCount, \"Too many fields for as_tuple(...)! add more if statements!\");\n"
            << "    }\n"
            << "}"
            << std::endl;
    
        return 0;
    }
    

    3、现有实施情况: Boost PFR

    你可能想退房 增压PFR -它正是您想要完成的:一个用于任意结构的类似元组的接口:

    促进PFR是一个C++14库,用于实现非常基本的反射。它允许您通过索引访问结构元素,并为用户定义的类型提供其他类似std::tuple的方法,而无需宏或样板代码

    示例: godbolt

    #include <boost/pfr.hpp>
    
    int main() {
        struct foo { char a; int b; };
        constexpr foo var{'A', 42};
    
        // converting to tuple
        constexpr auto tup = boost::pfr::structure_to_tuple(var);
        static_assert(std::same_as<const char, std::tuple_element_t<0, decltype(tup)>>);
        static_assert(std::get<0>(tup) == 'A');
        static_assert(std::get<1>(tup) == 42);
    
        // direct
        static_assert(boost::pfr::get<0>(var) == 'A');
        static_assert(boost::pfr::get<1>(var) == 42);
    }
    
    推荐文章