[英]Replace N-th element of a std::tuple
What is the shortest / best way to replace the n-th element of a tuple with a value (which may or may not have a different type)?用值(可能有也可能没有不同类型)替换元组的第 n 个元素的最短/最佳方法是什么? Solutions including c++20 are fine.
包括 c++20 在内的解决方案很好。 [EDIT: I would prefer something not requiring other libraries, but I'm still interested what solutions are possible with eg boost].
[编辑:我更喜欢不需要其他库的东西,但我仍然对使用例如 boost 可能的解决方案感兴趣]。
Ie: IE:
#include <cassert>
#include <tuple>
template<std::size_t N, ... >
auto replace_tuple_element( ... ) // <- Looking for a suitable implementation
struct Foo {
int value;
};
int main()
{
auto t1 = std::tuple{ 0, 1, 2, 3 };
auto t2 = replace_tuple_element<2>( t1, Foo{10} );
assert( std::get<0>(t2) == std::get<0>(t1));
assert( std::get<1>(t2) == std::get<1>(t1));
assert( std::get<2>(t2).value == 10);
assert( std::get<3>(t2) == std::get<3>(t1));
}
Note: Just replacing the n-th type in a typelist has eg be discussed here: How do I replace a tuple element at compile time?注意:仅替换类型列表中的第 n 个类型已经在这里讨论过: 如何在编译时替换元组元素? .
. But I also want to replace the value and hope that there are simpler/more elegant solutions now in c++20 than back when that question was asked.
但我也想替换这个值,并希望现在在 c++20 中有比问这个问题时更简单/更优雅的解决方案。
One solution I found for c++20 is this:我为 c++20 找到的一种解决方案是:
#include <cassert>
#include <tuple>
#include <type_traits>
template<std::size_t N, class TupleT, class NewT>
constexpr auto replace_tuple_element( const TupleT& t, const NewT& n )
{
constexpr auto tail_size = std::tuple_size<TupleT>::value - N - 1;
return [&]<std::size_t... I_head, std::size_t... I_tail>
( std::index_sequence<I_head...>, std::index_sequence<I_tail...> )
{
return std::tuple{
std::get<I_head>( t )...,
n,
std::get<I_tail + N + 1>( t )...
};
}(
std::make_index_sequence<N>{},
std::make_index_sequence<tail_size>{}
);
}
struct Foo {
int value;
};
int main()
{
auto t1 = std::tuple{ 0, 1, 2, 3 };
auto t2 = replace_tuple_element<2>( t1, Foo{10} );
assert( std::get<0>(t2) == std::get<0>(t1));
assert( std::get<1>(t2) == std::get<1>(t1));
assert( std::get<2>(t2).value == 10);
assert( std::get<3>(t2) == std::get<3>(t1));
}
What I like about the solution is that it is a single, self containied function.我喜欢这个解决方案的地方在于它是一个单一的、自包含的函数。 I wonder if there is something even shorter and/or more readable though.
我想知道是否有更短和/或更易读的东西。
Possible solution:可能的解决方案:
template<std::size_t i>
using index = std::integral_constant<std::size_t, i>;
template<std::size_t N, class Tuple, typename S>
auto replace_tuple_element(Tuple&& tuple, S&& s) {
auto get_element = [&tuple, &s]<std::size_t i>(Index<i>) {
if constexpr (i == N)
return std::forward<S>(s);
else
return std::get<i>(std::forward<Tuple>(tuple));
};
using T = std::remove_reference_t<Tuple>;
return [&get_element]<std::size_t... is>(std::index_sequence<is...>) {
return std::make_tuple(get_element(index<is>{})...);
}(std::make_index_sequence<std::tuple_size_v<T>>{});
}
Note this decays all element types, removing references and const.
请注意,这会衰减所有元素类型,删除引用和常量。
This amendment partially addresses this issue:该修正案部分解决了这个问题:
template<std::size_t N, class Tuple, typename S>
auto replace_tuple_element(Tuple&& tuple, S&& s) {
using T = std::remove_reference_t<Tuple>;
auto get_element = [&tuple, &s]<std::size_t i>(index<i>) {
if constexpr (i == N)
return std::forward<S>(s);
else
if constexpr (std::is_lvalue_reference_v<std::tuple_element_t<i, T>>)
return std::ref(std::get<i>(std::forward<Tuple>(tuple)));
else
return std::get<i>(std::forward<Tuple>(tuple));
};
return [&get_element]<std::size_t... is>(std::index_sequence<is...>) {
return std::make_tuple(get_element(index<is>{})...);
}(std::make_index_sequence<std::tuple_size_v<T>>{});
}
Now replace_tuple_element
also follows the convention of std::make_tuple
that converts std::reference_wrapper
arguments into references.现在
replace_tuple_element
也遵循std::make_tuple
,将std::reference_wrapper
参数转换为引用。 It does preserve reference types, but drops top-level constness.它确实保留了引用类型,但丢弃了顶级常量。
struct Foo {
Foo(int i) : value(i) {}
int value;
};
int main() {
int i = 1;
int j = 2;
auto t1 = std::make_tuple(std::make_unique<Foo>(0), std::ref(i), std::cref(j), 4);
static_assert(std::is_same_v<decltype(t1),
std::tuple<std::unique_ptr<Foo>, int&, const int&, int>>);
auto t2 = replace_tuple_element<1>(std::move(t1), std::make_unique<Foo>(5));
static_assert(std::is_same_v<decltype(t2),
std::tuple<std::unique_ptr<Foo>, std::unique_ptr<Foo>, const int&, int>>);
auto t3 = replace_tuple_element<0>(std::move(t2), std::cref(i));
static_assert(std::is_same_v<decltype(t3),
std::tuple<const int&, std::unique_ptr<Foo>, const int&, int>>);
auto t4 = replace_tuple_element<2>(std::move(t3), i);
static_assert(std::is_same_v<decltype(t4),
std::tuple<const int&, std::unique_ptr<Foo>, int, int>>);
}
template<std::size_t N, class U, class T>
auto replace_tuple_element(T&& t, U&& u) {
return [&]<std::size_t... I>(std::index_sequence<I...>) {
return std::tuple<std::conditional_t<I == N, U, std::tuple_element_t<I, std::decay_t<T>>>...>{
[&]() -> decltype(auto) {
if constexpr (I == N) return std::forward<U>(u);
else return static_cast<std::tuple_element_t<I, std::decay_t<T>>>(std::get<I>(t));
}()...};
}(std::make_index_sequence<std::tuple_size_v<std::decay_t<T>>>{});
}
You can remove some of the casts, forwards etc. if you're only concerned with value semantics.如果您只关心值语义,您可以删除一些强制转换、转发等。
The only thing new here is lambda template parameters to infer the indexing argument.这里唯一的新东西是用于推断索引参数的 lambda 模板参数。
If we want to both preserve all the types exactly as they are, and also do the same kind of reference unwrapping thing that the standard library typically does, then we need to make a small change to what the other implementations are here.如果我们既想完全保留所有类型,又想执行标准库通常所做的相同类型的引用解包操作,那么我们需要对此处的其他实现进行一些小的更改。
unwrap_ref_decay
will do a decay_t
on the type, and then turn reference_wrapper<T>
into T&
. unwrap_ref_decay
会对类型做一个decay_t
,然后把reference_wrapper<T>
变成T&
。 And using Boost.Mp11 for a few things that just make everything nicer:使用 Boost.Mp11 做一些让一切变得更好的事情:
template <size_t N, typename OldTuple, typename NewType>
constexpr auto replace_tuple_element(OldTuple&& tuple, NewType&& elem)
{
using Old = std::remove_cvref_t<OldTuple>;
using R = mp_replace_at_c<Old, N, std::unwrap_ref_decay_t<NewType>>;
static constexpr auto Size = mp_size<Old>::value;
auto get_nth = [&](auto I) -> decltype(auto) {
if constexpr (I == N) return std::forward<NewType>(elem);
else return std::get<I>(std::forward<OldTuple>(tuple));
};
return [&]<size_t... Is>(std::index_sequence<Is...>) {
return R(get_nth(mp_size_t<Is>())...);
}(std::make_index_sequence<Size>());
}
This implementation means that given:这个实现意味着给定:
std::tuple<int const, int const> x(1, 2);
int i = 42;
auto y = replace_tuple_element<1>(x, std::ref(i));
y
is a tuple<int const, int&>
. y
是一个tuple<int const, int&>
。
This is a good use case for a counterpart of tuple_cat
that, instead of concatenating tuples, gives you a slices of a tuple.这是
tuple_cat
一个很好的用例,它不是连接元组,而是给你一个元组的切片。 Unfortunately, this doesn't exist in the standard library, so we'll have to write it ourselves:不幸的是,这在标准库中不存在,所以我们必须自己编写:
template <std::size_t Begin, std::size_t End, typename Tuple>
constexpr auto tuple_slice(Tuple&& t)
{
return [&]<std::size_t... Ids> (std::index_sequence<Ids...>)
{
return std::tuple<std::tuple_element_t<Ids, std::remove_reference_t<Tuple>>...>
{std::get<Begin + Ids>(std::forward<Tuple>(t))...};
} (std::make_index_sequence<End - Begin>{});
}
Just like tuple_cat
, this preserve the exact same types of the original tuple.就像
tuple_cat
一样,它保留了与原始元组完全相同的类型。
With tuple_cat
and tuple_slice
, the implementation of replace_tuple_element
feels quite elegant:使用
tuple_cat
和tuple_slice
, replace_tuple_element
的实现感觉很优雅:
template <std::size_t N, typename Tuple, typename T>
constexpr auto replace_tuple_element(Tuple&& tuple, T&& t)
{
constexpr auto Size = std::tuple_size_v<std::remove_reference_t<Tuple>>;
return std::tuple_cat(
tuple_slice<0, N>(std::forward<Tuple>(tuple)),
std::make_tuple(std::forward<T>(t)),
tuple_slice<N + 1, Size>(std::forward<Tuple>(tuple))
);
}
Using make_tuple
preserves the behavior of turning reference_wrapper<T>
into T&
.使用
make_tuple
保留了将reference_wrapper<T>
转换为T&
。 Demo演示
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.