什么应该tuple_mapreturn?
What should tuple_map return?
我想实现一个通用的 tuple_map
函数,它接受一个仿函数和一个 std::tuple
,将仿函数应用于该元组的每个元素,并且 return 是一个 std::tuple
的结果。实现非常简单,但是问题出现了:这个函数 return 应该是什么类型?我的实现使用了 std::make_tuple
。但是,建议 here std::forward_as_tuple
。
更具体地说,实现(为简洁起见省略了空元组的处理):
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)
{
return std::make_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^
}
template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)
{
return std::forward_as_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^
}
template<class Tuple, class Fn>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple)
{
return tuple_map_v(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}
template<class Tuple, class Fn>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple)
{
return tuple_map_r(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}
在案例 1 中,我们使用 std::make_tuple
来衰减每个参数的类型(_v
作为值),在案例 2 中,我们使用 std::forward_as_tuple
来保留引用(_r
供参考)。这两种情况各有利弊。
悬空引用。
auto copy = [](auto x) { return x; };
auto const_id = [](const auto& x) -> decltype(auto) { return x; };
auto r1 = tuple_map_v(copy, std::make_tuple(1));
// OK, type of r1 is std::tuple<int>
auto r2 = tuple_map_r(copy, std::make_tuple(1));
// UB, type of r2 is std::tuple<int&&>
std::tuple<int> r3 = tuple_map_r(copy, std::make_tuple(1));
// Still UB
std::tuple<int> r4 = tuple_map_r(const_id, std::make_tuple(1));
// OK now
引用元组。
auto id = [](auto& x) -> decltype(auto) { return x; };
int a = 0, b = 0;
auto r1 = tuple_map_v(id, std::forward_as_tuple(a, b));
// Type of r1 is std::tuple<int, int>
++std::get<0>(r1);
// Increments a copy, a is still zero
auto r2 = tuple_map_r(id, std::forward_as_tuple(a, b));
// Type of r2 is std::tuple<int&, int&>
++std::get<0>(r2);
// OK, now a = 1
仅移动类型。
NonCopyable nc;
auto r1 = tuple_map_v(id, std::forward_as_tuple(nc));
// Does not compile without a copy constructor
auto r2 = tuple_map_r(id, std::forward_as_tuple(nc));
// OK, type of r2 is std::tuple<NonCopyable&>
引用std::make_tuple
。
auto id_ref = [](auto& x) { return std::reference_wrapper(x); };
NonCopyable nc;
auto r1 = tuple_map_v(id_ref, std::forward_as_tuple(nc));
// OK now, type of r1 is std::tuple<NonCopyable&>
auto r2 = tuple_map_v(id_ref, std::forward_as_tuple(a, b));
// OK, type of r2 is std::tuple<int&, int&>
(可能是我弄错了或者漏掉了一些重要的东西。)
似乎 make_tuple
是可行的方法:它不会产生悬挂引用,而且仍然可以强制推导引用类型。您将如何实施 tuple_map
(以及与之相关的陷阱是什么)?
您在问题中强调的问题是,在按值 returns 的仿函数上使用 std::forward_as_tuple
会在结果元组中留下右值引用。
使用 make_tuple
不能保留左值引用,但是使用 forward_as_tuple
不能保留普通值。您可以改为依靠 std::invoke_result
来找出结果元组必须包含的类型,并使用适当的 std::tuple
构造函数。
template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>) {
using tuple_type = std::tuple<
typename std::invoke_result<
Fn, decltype(std::get<indices>(std::forward<Tuple>(tuple)))
>::type...
>;
return tuple_type(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
}
这样您就可以保留 fn
调用结果的值类别。
Live demo on Coliru
我想实现一个通用的 tuple_map
函数,它接受一个仿函数和一个 std::tuple
,将仿函数应用于该元组的每个元素,并且 return 是一个 std::tuple
的结果。实现非常简单,但是问题出现了:这个函数 return 应该是什么类型?我的实现使用了 std::make_tuple
。但是,建议 here std::forward_as_tuple
。
更具体地说,实现(为简洁起见省略了空元组的处理):
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)
{
return std::make_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^
}
template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)
{
return std::forward_as_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^
}
template<class Tuple, class Fn>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple)
{
return tuple_map_v(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}
template<class Tuple, class Fn>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple)
{
return tuple_map_r(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}
在案例 1 中,我们使用 std::make_tuple
来衰减每个参数的类型(_v
作为值),在案例 2 中,我们使用 std::forward_as_tuple
来保留引用(_r
供参考)。这两种情况各有利弊。
悬空引用。
auto copy = [](auto x) { return x; }; auto const_id = [](const auto& x) -> decltype(auto) { return x; }; auto r1 = tuple_map_v(copy, std::make_tuple(1)); // OK, type of r1 is std::tuple<int> auto r2 = tuple_map_r(copy, std::make_tuple(1)); // UB, type of r2 is std::tuple<int&&> std::tuple<int> r3 = tuple_map_r(copy, std::make_tuple(1)); // Still UB std::tuple<int> r4 = tuple_map_r(const_id, std::make_tuple(1)); // OK now
引用元组。
auto id = [](auto& x) -> decltype(auto) { return x; }; int a = 0, b = 0; auto r1 = tuple_map_v(id, std::forward_as_tuple(a, b)); // Type of r1 is std::tuple<int, int> ++std::get<0>(r1); // Increments a copy, a is still zero auto r2 = tuple_map_r(id, std::forward_as_tuple(a, b)); // Type of r2 is std::tuple<int&, int&> ++std::get<0>(r2); // OK, now a = 1
仅移动类型。
NonCopyable nc; auto r1 = tuple_map_v(id, std::forward_as_tuple(nc)); // Does not compile without a copy constructor auto r2 = tuple_map_r(id, std::forward_as_tuple(nc)); // OK, type of r2 is std::tuple<NonCopyable&>
引用
std::make_tuple
。auto id_ref = [](auto& x) { return std::reference_wrapper(x); }; NonCopyable nc; auto r1 = tuple_map_v(id_ref, std::forward_as_tuple(nc)); // OK now, type of r1 is std::tuple<NonCopyable&> auto r2 = tuple_map_v(id_ref, std::forward_as_tuple(a, b)); // OK, type of r2 is std::tuple<int&, int&>
(可能是我弄错了或者漏掉了一些重要的东西。)
似乎 make_tuple
是可行的方法:它不会产生悬挂引用,而且仍然可以强制推导引用类型。您将如何实施 tuple_map
(以及与之相关的陷阱是什么)?
您在问题中强调的问题是,在按值 returns 的仿函数上使用 std::forward_as_tuple
会在结果元组中留下右值引用。
使用 make_tuple
不能保留左值引用,但是使用 forward_as_tuple
不能保留普通值。您可以改为依靠 std::invoke_result
来找出结果元组必须包含的类型,并使用适当的 std::tuple
构造函数。
template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>) {
using tuple_type = std::tuple<
typename std::invoke_result<
Fn, decltype(std::get<indices>(std::forward<Tuple>(tuple)))
>::type...
>;
return tuple_type(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
}
这样您就可以保留 fn
调用结果的值类别。
Live demo on Coliru