考虑以下 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
的目的。这是一个演示。
有人知道如何更好地做到这一点,即不调用任何复制/移动构造函数吗?
您有额外的副本,使用
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)...))) {}
// ...
};