如何通过标准元组操作正确转发和使用 constexpr 结构的嵌套元组

Chr*_* G. 3 c++ tuples variadic-templates constexpr c++20

我想通过constexpr constructor a存储传递的数据struct,并将数据存储在 a 中std::tuple,以执行各种 TMP/编译时操作。

执行

template <typename... _Ts>
struct myInitializer {
    std::tuple<_Ts...> init_data;

    constexpr myInitializer(_Ts&&... _Vs) 
        : init_data{ std::tuple(std::forward<_Ts>(_Vs)...) }
    {}
};
Run Code Online (Sandbox Code Playgroud)

存储的数据使用轻量级strong type结构,通过左值和右值帮助器重载生成:

template <typename T, typename... Ts>
struct data_of_t {
    using type = T;
    using data_t = std::tuple<Ts...>;
    data_t data;

    constexpr data_of_t(Ts&&... _vs)
        : data(std::forward<Ts>(_vs)...)
    {}
};
template<typename T, typename... Ts>
constexpr auto data_of(Ts&&... _vs) {
    return data_of_t<T, Ts...>(std::forward<Ts>(_vs)...);
};

template<typename T, typename... Ts>
constexpr auto data_of(Ts&... _vs) {
    return data_of_t<T, Ts...>(std::forward<Ts>(_vs)...);
};
Run Code Online (Sandbox Code Playgroud)

它的实现就像

template <typename T = int>
class test {
public:
    static constexpr auto func(int p0=0, int p1=1, int p2=3) noexcept {
        return data_of <test<T>>
            (data_of<test<T>>(p0, p1));
    }
};
Run Code Online (Sandbox Code Playgroud)
int main() {
    constexpr // fails to run constexpr // works without
    auto init = myInitializer (
        test<int>::func()
        ,test<int>::func(3)
        ,test<int>::func(4,5)
    );

    std::apply([&](auto&&... args) {
        //std::cout << __PRETTY_FUNCTION__ << std::endl;
        auto merged_tuple = std::tuple_cat(std::forward<decltype(args.data)>(args.data)...);
        }
        , init.init_data);
}
Run Code Online (Sandbox Code Playgroud)

进入正题

如果 myInitializer 实例为 ,则 std::tuple_cat 失败constexpr

std::apply([&](auto&&... args) {
        auto merged_tuple = std::tuple_cat(std::forward<decltype(args.data)>(args.data)...);
Run Code Online (Sandbox Code Playgroud)

const它似乎与通过 添加的限定符有关constexpr

如何解决这个问题?

请参阅https://godbolt.org/z/j5xdT39aE的完整示例

Bar*_*rry 5

这:

auto merged_tuple = std::tuple_cat(std::forward<decltype(args.data)>(args.data)...);
Run Code Online (Sandbox Code Playgroud)

不是转发数据的正确方式。decltype(args.data)将为您提供该数据成员的类型 - 这不是 的常量或值类别的函数args。让我们举一个更简单的例子:

void f(auto&& arg) {
    g(std::forward<decltype(arg.data)>(arg.data));
}

struct C { int data; };

C c1{1};
const C c2{2};

f(c1); 
f(c2);
f(C{3});
Run Code Online (Sandbox Code Playgroud)

所以这里我有三个调用f(分别调用f<C&>f<const C&>f<C>)。在所有这三种情况下,decltype(arg.data)都是...只是int。这就是类型C::data。但这不是它需要转发的方式(它不会编译,c2因为我们试图抛弃 const-ness - 就像你的例子一样 - 并且它会错误地移出c1)。

您想要的是arg单独转发 ,然后访问数据:

void f(auto&& arg) {
    g(std::forward<decltype(arg)>(arg).data);
}
Run Code Online (Sandbox Code Playgroud)

现在,decltype(arg)实际上从实例化到实例化都有所不同,这是一个很好的指标,表明我们正在做一些明智的事情。