如何在不添加额外副本的情况下拆分可变参数模板参数?

问题描述 投票:0回答:1

考虑以下 C++23 代码:

#include <tuple>
#include <cstdio>
#include <iostream>

struct W1
{
    int n;
    bool b;
    W1(int x, bool f) : n(x), b(f) {}
};


struct W2
{
    long n;
    W2(long x) : n(x) {}
    W2(const W2& other) : n(other.n) { std::puts("copy ctor"); }
    W2(W2&& other) : n(other.n) { std::puts("move ctor"); }
};

int main() {
    Foo<W1, W2, W2> foo{std::in_place_type<W1>, 42, true,
                        std::in_place_type<W2>, 7L,
                        std::in_place_type<W2>, 9};

    std::cout << std::tuple_size_v<decltype(foo.data)> << '\n';
    std::cout << get<0>(foo.data).n << '\n';
    std::cout << get<0>(foo.data).b << '\n';
    std::cout << get<1>(foo.data).n << '\n';
    std::cout << get<2>(foo.data).n << '\n';

    return 0;
}

这里,

Foo
是一个类模板,它有一个
std::tuple
数据成员
data
(为了简单起见,忽略封装),在本例中它由三个对象组成:
W1{42, true}
W2{7L}
 W2{9}

我的问题是:如何创建符合上面所示用法的类

Foo
(请注意,
Foo
应该能够构造任意数量的任何类型的对象)

我已经让它像这样工作了:

#include <tuple>

template <typename>
inline constexpr bool is_in_place_type_v = false;

template <typename T>
inline constexpr bool is_in_place_type_v<std::in_place_type_t<T>> = true;

template <std::size_t Offset, std::size_t... Ints>
auto make_offset_sequence_impl(std::index_sequence<Ints...>) {
    return std::index_sequence<Offset + Ints...>{};
}

template <std::size_t Offset, std::size_t N>
auto make_offset_sequence() {
    return make_offset_sequence_impl<Offset>(std::make_index_sequence<N>());
}

// helper type that effectively allows for splitting a tuple into two
// , based on the position of `std::in_place_type`
template <int N, typename... Types>
struct Indexer
{};

// end-case
template <int N>
struct Indexer<N>
{
    using arg_seq = decltype(std::make_index_sequence<N>());
    using rest_seq = std::index_sequence<>;
    using type = Indexer;
};

template <int N, typename Type, typename... Types>
struct Indexer<N, Type, Types...>
{
    using type = Indexer<N + 1, Types...>::type;
};

template <int N, typename PT, typename... Types>
requires is_in_place_type_v<std::decay_t<PT>>
struct Indexer<N, PT, Types...>
{
    using arg_seq = decltype(std::make_index_sequence<N>());
    using rest_seq = decltype(make_offset_sequence<N, sizeof...(Types) + 1>());

    using type = Indexer;
    using next = Indexer<N, Types...>::type;
};

template <typename... Types>
struct Foo
{
    std::tuple<Types...> data;

    static auto m_init() { return std::tuple{}; }

    template <typename T, typename... Ts>
    static auto m_init(const std::in_place_type_t<T>&, Ts&&... args) {

        using indices = Indexer<0, std::in_place_type_t<T>, Ts...>::next;

        auto lambda1 = [&args...]<std::size_t... Is>(std::index_sequence<Is...>)
        {
            return std::tuple{
                    T{std::get<Is>(std::tuple{std::forward<Ts>(args)...})...}
                };
        };

        auto lambda2 = [&args...]<std::size_t... Is>(std::index_sequence<Is...>) {
            return m_init(std::get<Is>(std::tuple{std::forward<Ts>(args)...})...);
        };

        return std::tuple_cat(lambda1(typename indices::arg_seq{}),
                              lambda2(typename indices::rest_seq{}));
    }

    template <typename... Ts>
    explicit Foo(Ts&&... ts) : data(m_init(std::forward<Ts>(ts)...)) {}
};

但是,这会比常规构造函数(如

Foo foo{W1{42, true}, W2{7L}, W2{9}};
使用)产生更多的副本/移动,从而违背了
std::in_place_type
的目的。这是一个演示

有人知道如何更好地做到这一点,即不调用任何复制/移动构造函数吗?

c++ tuples variadic-templates template-meta-programming in-place
1个回答
0
投票

您有额外的副本,使用

std::tuple
而不是
std::forward_as_tuple

此外,你还有额外的“递归”副本

std::tuple_cat

对于后者,您可以连接工厂:

template <typename... Types>
struct Foo
{
    std::tuple<Types...> data;

    static auto m_init() { return std::tuple{}; }

    template <typename T, typename... Ts>
    static auto m_init(const std::in_place_type_t<T>&, Ts&&... args) {
        using indices = Indexer<0, std::in_place_type_t<T>, Ts...>::next;

        auto lambda1 = [&args...]<std::size_t... Is>(std::index_sequence<Is...>)
        {
            return std::tuple(
                [&](){ return T{std::get<Is>(std::forward_as_tuple(std::forward<Ts>(args)...))...}; });
        };
        auto lambda2 = [&args...]<std::size_t... Is>(std::index_sequence<Is...>) {
            return m_init(std::get<Is>(std::forward_as_tuple(std::forward<Ts>(args)...))...);
        };

        return std::tuple_cat(lambda1(typename indices::arg_seq{}), lambda2(typename indices::rest_seq{}));
    }

    template <typename... Ts>
    explicit Foo(Ts&&... ts) : data(std::apply([](auto... factories){ return std::tuple{factories()...}; }, m_init(std::forward<Ts>(ts)...))) {}

// ...
};

演示

© www.soinside.com 2019 - 2024. All rights reserved.