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

发布于 2025-02-02 03:24:00 字数 2197 浏览 2 评论 0原文

我想通过constexpr struct> struct> struct 存储传递的数据,然后将数据存储在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)...) }
    {}
};

存储的数据使用轻量级强类型结构,通过lvalue和rvalue助手重载生成:

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)...);
};

它是实现的,例如

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));
    }
};
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);
}

到达点

std :: tuple_cat如果MyInitializer实例为constexpr,则会失败。

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

它似乎与const通过constexpr添加的预选赛有关。

如何修复?

请参阅

I want to store passed data via constexpr constructor of a struct, and store the data in a std::tuple, to perform various TMP / compile time operations.

Implementation

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

    constexpr myInitializer(_Ts&&... _Vs) 
        : init_data{ std::tuple(std::forward<_Ts>(_Vs)...) }
    {}
};

Stored data uses a lightweight strong type struct, generated via lvalue and rvalue helper overload:

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)...);
};

It's implemented like

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));
    }
};
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);
}

Getting to the point

std::tuple_cat fails if myInitializer instance is constexpr.

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

It appears to be related to the const qualifier added via constexpr.

How can this be fixed?

See full example at https://godbolt.org/z/j5xdT39aE

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

聚集的泪 2025-02-09 03:24:00

这:

auto merged_tuple = std::tuple_cat(std::forward<decltype(args.data)>(args.data)...);

不是转发数据的正确方法。 code> exltype(args.data)将为您提供该数据成员的类型 - 这不是args args的const -ness或值类别的函数。让我们以一个更简单的示例:

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});

在这里我有三个呼叫f(呼叫f&lt; c&amp;&gt;f&lt; const c&amp c&amp;&gt; 和f&lt; c&gt;)。在所有三种情况下,code> nectType(arg.data) is ... Just int。这就是c :: data的类型。但这不是不是需要如何转发它(它不会为c2编译,因为我们正在尝试将const-抛弃 - 就像您的示例一样 - 并且它会错误地移出c1)。

您想要的是转发arg,分别和然后访问数据:

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

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

This:

auto merged_tuple = std::tuple_cat(std::forward<decltype(args.data)>(args.data)...);

is not the right way to forward data. decltype(args.data) is going to give you the type of that data member - which is not a function of either the const-ness or value category of args. Let's take a simpler example:

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});

So here I have three calls to f (which call f<C&>, f<const C&>, and f<C>, respectively). In all three cases, decltype(arg.data) is... just int. That's what the type of C::data is. But that's not how it needs to be forwarded (it won't compile for c2 because we're trying to cast away const-ness -- as in your example -- and it'll erroneously move out of c1).

What you want is to forward arg, separately, and then access data:

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

Now, decltype(arg) actually varies from instantiation to instantiation, which is a good indicator that we're doing something sensible.

暮倦 2025-02-09 03:24:00

除了Barry表示的转发问题外,您不能在Init上拥有ConstexPR的原因不同。这是因为您在data_of_t中包含对临时性的引用。

您会看到,您包含了从转发参考获得的从过载分辨率获得的类型:

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

在这种情况下,ts ...可能是int,float const&amp;,double&amp; 。您将这些参考类型发送,然后将它们包含在std :: tuple in data_of_t中。

这些临时性是test函数的本地变量:

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));
    }
};

这里的问题是p0p1p2都是局部变数。您将它们发送到包含对它们的引用的test_of_t中,然后返回包含所有引用对本地变量的对象。这也许是MSVC崩溃的原因。需要编译器为ConstexPR上下文中的任何未定义行为提供诊断。此崩溃是100%的编译器错误,您应该报告它。

那么您如何解决呢?

简而言之,不要通过更改data_of

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

这将衰减类型,从而删除引用并衰减对指针的任何引用。

然后,您必须更改构造函数。您可以在其中调用std :: forward,但是如果您在模板参数中衰减,则不会发生转发。

template<typename... Vs> requires((std::same_as<std::decay_t<Vs>, Ts>) && ...)
constexpr data_of_t(Vs... _vs)
    : data(std::forward<Vs>(_vs)...)
{}

这将添加适当的转发并正确约束,以便它始终按预期的data_of进行操作。

仅执行这些更改将从代码中删除UB,但也将其更改。类型data_of_t将始终包含值,并且不会包含引用。如果要发送引用,您将需要std :: ref之类的东西,就像std :: bind必须使用以推迟参数。

您仍然需要使用std :: forward&lt; exptype(arg)&gt;(arg).data正确转发如@barry所述

In addition of the forwarding problem denoted by Barry, there's a different reason why you cannot have constexpr on init. This is because you contain a reference to a temporary inside data_of_t.

You see, you are containing a type obtained from overload resolution from a forwarding reference:

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

The Ts... in this case could be something like int, float const&, double&. You send those reference type and then you contain them inside of the std::tuple in data_of_t.

Those temporaries are local variables from the test function:

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));
    }
};

The problem here is that p0, p1, p2 are all local variable. You send them in test_of_t which will contain references to them, and you return the object containing all those reference to the local variable. This is maybe the cause of the MSVC crash. Compiler are required to provide diagnostic for any undefined behaviour in constexpr context. This crash is 100% a compiler bug and you should report it.

So how do you fix that?

Simply don't contain references by changing data_of:

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

This will decay the type thus removing the references and decay any reference to C array to pointers.

Then, you have to change your constructor. You call std::forward in there but it's no forwarding occurring if you decay in the template arguments.

template<typename... Vs> requires((std::same_as<std::decay_t<Vs>, Ts>) && ...)
constexpr data_of_t(Vs... _vs)
    : data(std::forward<Vs>(_vs)...)
{}

This will add proper forwarding and also constrain it properly so it always do as data_of intended.

Just doing those change will remove UB from the code, but also change it a bit. The type data_of_t will always contain values, and won't contain references. If you want to send a reference, you will need something like std::ref, just like std::bind have to use to defer parameters.

You will still need to use std::forward<decltype(arg)>(arg).data for proper forwarding as @Barry stated

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文