std::apply 如何在没有显式 std::forward 的情况下转发参数?
How does std::apply forward parameters without explicit std::forward?
考虑 std::apply
的可能实现:
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F &&f, Tuple &&t, std::index_sequence<I...>)
{
return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}
} // namespace detail
template <class F, class Tuple>
constexpr decltype(auto) apply(F &&f, Tuple &&t)
{
return detail::apply_impl(
std::forward<F>(f), std::forward<Tuple>(t),
std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{});
}
为什么调用函数(f
) 时传递参数元组(t
) 我们不需要对元组的每个元素执行std::forward
std::get<I>(std::forward<Tuple>(t))...
正在执行中?
std::forward
用于确保所有内容都以正确的值类别到达调用站点。
但是元组的每个成员都是左值,即使它是 rvalue
个引用的元组。
您不需要 std::forward
每个元素,因为 std::get
已为元组的右值引用和左值引用重载。
std::forward<Tuple>(t)
会给你一个左值 (Tuple &
) 或一个右值 (Tuple &&
),根据你得到的,std::get
会给你一个T &
(左值)或 T &&
(右值)。查看 std::get
.
的各种重载
关于 std::tuple
和 std::get
-
的一些细节
如所述,元组的每个成员都是左值,无论它是从右值还是左值构造的,在这里都没有关系:
double a{0.0};
auto t1 = std::make_tuple(int(), a);
auto t2 = std::make_tuple(int(), double());
问题是 - 元组是右值吗?如果是,你可以移动它的成员,如果不是,你必须做一个副本,但是std::get
已经通过返回相应类别的成员来解决这个问题。
decltype(auto) a1 = std::get<0>(t1);
decltype(auto) a2 = std::get<0>(std::move(t1));
static_assert(std::is_same<decltype(a1), int&>{}, "");
static_assert(std::is_same<decltype(a2), int&&>{}, "");
回到具体的例子 std::forward
:
template <typename Tuple>
void f(Tuple &&tuple) { // tuple is a forwarding reference
decltype(auto) a = std::get<0>(std::forward<Tuple>(tuple));
}
f(std::make_tuple(int())); // Call f<std::tuple<int>>(std::tuple<int>&&);
std::tuple<int> t1;
f(t1); // Call f<std::tuple<int>&>(std::tuple<int>&);
在 f
的第一次调用中,a
的类型将是 int&&
因为 tuple
将作为 std::tuple<int>&&
转发,而在第二种情况它的类型将是 int&
因为 tuple
将被转发为 std::tuple<int>&
.
考虑 std::apply
的可能实现:
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F &&f, Tuple &&t, std::index_sequence<I...>)
{
return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}
} // namespace detail
template <class F, class Tuple>
constexpr decltype(auto) apply(F &&f, Tuple &&t)
{
return detail::apply_impl(
std::forward<F>(f), std::forward<Tuple>(t),
std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{});
}
为什么调用函数(f
) 时传递参数元组(t
) 我们不需要对元组的每个元素执行std::forward
std::get<I>(std::forward<Tuple>(t))...
正在执行中?
std::forward
用于确保所有内容都以正确的值类别到达调用站点。
但是元组的每个成员都是左值,即使它是 rvalue
个引用的元组。
您不需要 std::forward
每个元素,因为 std::get
已为元组的右值引用和左值引用重载。
std::forward<Tuple>(t)
会给你一个左值 (Tuple &
) 或一个右值 (Tuple &&
),根据你得到的,std::get
会给你一个T &
(左值)或 T &&
(右值)。查看 std::get
.
关于 std::tuple
和 std::get
-
如
double a{0.0};
auto t1 = std::make_tuple(int(), a);
auto t2 = std::make_tuple(int(), double());
问题是 - 元组是右值吗?如果是,你可以移动它的成员,如果不是,你必须做一个副本,但是std::get
已经通过返回相应类别的成员来解决这个问题。
decltype(auto) a1 = std::get<0>(t1);
decltype(auto) a2 = std::get<0>(std::move(t1));
static_assert(std::is_same<decltype(a1), int&>{}, "");
static_assert(std::is_same<decltype(a2), int&&>{}, "");
回到具体的例子 std::forward
:
template <typename Tuple>
void f(Tuple &&tuple) { // tuple is a forwarding reference
decltype(auto) a = std::get<0>(std::forward<Tuple>(tuple));
}
f(std::make_tuple(int())); // Call f<std::tuple<int>>(std::tuple<int>&&);
std::tuple<int> t1;
f(t1); // Call f<std::tuple<int>&>(std::tuple<int>&);
在 f
的第一次调用中,a
的类型将是 int&&
因为 tuple
将作为 std::tuple<int>&&
转发,而在第二种情况它的类型将是 int&
因为 tuple
将被转发为 std::tuple<int>&
.