任何 C++20 alternative/tricks 到 P1061 "Structured Bindings can introduce a Pack"?

Any C++20 alternative/tricks to P1061 "Structured Bindings can introduce a Pack"?

有没有办法使用具有任意数量身份的结构化绑定?

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

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

你可以通过为每个不同数量的参数提供一个实现来解决这个问题(就像你的例子一样)


1。使用 if constexpr

您可以通过使用 if constexpr 稍微简化它 - 如果条件不为真,则整个语句将被丢弃(并且对于未实例化的模板化实体,即它不必编译给定的模板类型)。
auto return 类型仅从 non-discarded return 语句推断出 return 类型,因此您可以使用 if constexpr 更改 return函数类型。

这包括:

8.5.2 (2) The if statement

If the value of the converted condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. During the instantiation of an enclosing templated entity, if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.

9.2.9.6.1 (3) Placeholder type specifiers

If the declared return type of the function contains a placeholder type, the return type of the function is deduced from non-discarded return statements, if any, in the body of the function.

这允许您像这样编写 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));
}

然后我们需要做的就是生成 if constexpr 个分支,达到您要支持的最大成员数量。

注意:如果你愿意你也可以用std::tie(...)替换std::tuple(...)到return一个引用元组到原始成员而不是复制值的元组。 (godbolt example)


2。生成分支

2.1 使用 Boost Preprocessor

Boost Preprocessor 提供了很多方便的宏,我们可以使用它们轻松生成所需的分支。

在这个例子中我们只需要其中的两个宏:

  • 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 个 if constexpr

增强 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,您将不得不手动编写大量样板宏代码 - 不幸的是,没有办法解决这个问题。

此示例使用 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 编写代码生成器

除了使用宏生成代码,您还可以编写一个程序,为您的实际程序生成 header 作为 build-setup.

的一部分

这使代码更易于阅读(没有宏恶作剧)- 您只需为要支持的成员数量生成一次代码。

示例: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

您可能想查看 Boost PFR - 它完全符合您的要求:一个 tuple-like 任意结构的接口:

Boost.PFR is a C++14 library for a very basic reflection. It gives you access to structure elements by index and provides other std::tuple like methods for user defined types without macro or boilerplate code

示例: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);
}