将一系列元组解包为 n 元函数
Unpacking a range of tuples into n-ary function
假设我有一系列元组,例如来自 zip 函数。在该范围上运行的函数是否必须始终是一元的,或者是否存在将元组解压缩到函数参数中的某种转换。基本上,我想做以下事情:
auto r1 = {1, 2, 3, 4};
auto r2 = {'a', 'b', 'c', 'd'};
auto chars = view::zip(r1, r2) | view::transform([](int a, char x) { return x; });
而不是显式使用 std::tie 或 std::apply。
听起来您真正需要的是一个分解元组参数的函数适配器。像这样 (LIVE):
#include <type_traits>
#include <utility>
#include <range/v3/core.hpp>
#include <range/v3/utility/semiregular.hpp>
#include <range/v3/utility/tuple_algorithm.hpp>
template<class F>
struct decomposed_fn
{
private:
CONCEPT_ASSERT(ranges::CopyConstructible<F>());
ranges::semiregular_t<F> f_;
template<class FF>
struct caller
{
FF &f_;
template<class... Args>
RANGES_CXX14_CONSTEXPR auto operator()(Args &&...args)
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
ranges::invoke(f_, std::forward<Args>(args)...)
)
};
public:
decomposed_fn() = default;
RANGES_CXX14_CONSTEXPR explicit decomposed_fn(F f)
noexcept(std::is_nothrow_move_constructible<F>::value)
: f_(std::move(f))
{}
template<class T>
RANGES_CXX14_CONSTEXPR auto operator()(T &&t)
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
ranges::tuple_apply(caller<F>{f_}, std::forward<T>(t))
)
template<class T>
RANGES_CXX14_CONSTEXPR auto operator()(T &&t) const
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
ranges::tuple_apply(caller<F const>{f_}, std::forward<T>(t))
)
};
template<class F,
CONCEPT_REQUIRES_(ranges::CopyConstructible<std::decay_t<F>>())>
RANGES_CXX14_CONSTEXPR auto decomposed(F &&f)
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
decomposed_fn<std::decay_t<F>>(std::forward<F>(f))
)
你可以用它来制定你的范围:
auto chars = view::zip(r1, r2)
| view::transform(decomposed([](int, char x) { return x; }));
遗憾的是,似乎没有 transform-apply
视图。一个像其他答案一样的简单解决方案是调整您的 lambda,以便使用 std::apply
(相当于 std::bind_front(std::apply<...>, your_lambda)
)
调用它
// C++20
template<typename F>
constexpr auto apply_to(F&& f) noexcept(noexcept([f=static_cast<F&&>(f)]{})) {
return [f=static_cast<F&&>(f)]<typename Tuple>(Tuple&& tuple) noexcept(noexcept(::std::apply(f, static_cast<Tuple&&>(tuple)))) -> decltype(auto) {
return ::std::apply(f, static_cast<Tuple&&>(tuple));
};
}
// C++17
// (Or C++14 with another std::apply implementation, like ranges::tuple_apply)
template<typename F>
constexpr auto apply_to(F&& f) {
return [f=static_cast<F&&>(f)](auto&& tuple) noexcept(noexcept(::std::apply(f, static_cast<decltype(tuple)&&>(tuple)))) -> decltype(auto) {
return ::std::apply(f, static_cast<decltype(tuple)&&>(tuple));
};
}
并将您的 lambda 包装为 apply_to([](int a, char x) { /*...*/ })
。
或者,结构化绑定非常短(在 C++17 中)
// Be careful about excessive copying. Fine for the simple
// `std::tuple<char, int>`, but consider forwarding references
auto chars = view::zip(r1, r2) | view::transform([](auto zipped) {
auto [a, x] = zipped;
return x;
});
假设我有一系列元组,例如来自 zip 函数。在该范围上运行的函数是否必须始终是一元的,或者是否存在将元组解压缩到函数参数中的某种转换。基本上,我想做以下事情:
auto r1 = {1, 2, 3, 4};
auto r2 = {'a', 'b', 'c', 'd'};
auto chars = view::zip(r1, r2) | view::transform([](int a, char x) { return x; });
而不是显式使用 std::tie 或 std::apply。
听起来您真正需要的是一个分解元组参数的函数适配器。像这样 (LIVE):
#include <type_traits>
#include <utility>
#include <range/v3/core.hpp>
#include <range/v3/utility/semiregular.hpp>
#include <range/v3/utility/tuple_algorithm.hpp>
template<class F>
struct decomposed_fn
{
private:
CONCEPT_ASSERT(ranges::CopyConstructible<F>());
ranges::semiregular_t<F> f_;
template<class FF>
struct caller
{
FF &f_;
template<class... Args>
RANGES_CXX14_CONSTEXPR auto operator()(Args &&...args)
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
ranges::invoke(f_, std::forward<Args>(args)...)
)
};
public:
decomposed_fn() = default;
RANGES_CXX14_CONSTEXPR explicit decomposed_fn(F f)
noexcept(std::is_nothrow_move_constructible<F>::value)
: f_(std::move(f))
{}
template<class T>
RANGES_CXX14_CONSTEXPR auto operator()(T &&t)
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
ranges::tuple_apply(caller<F>{f_}, std::forward<T>(t))
)
template<class T>
RANGES_CXX14_CONSTEXPR auto operator()(T &&t) const
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
ranges::tuple_apply(caller<F const>{f_}, std::forward<T>(t))
)
};
template<class F,
CONCEPT_REQUIRES_(ranges::CopyConstructible<std::decay_t<F>>())>
RANGES_CXX14_CONSTEXPR auto decomposed(F &&f)
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
decomposed_fn<std::decay_t<F>>(std::forward<F>(f))
)
你可以用它来制定你的范围:
auto chars = view::zip(r1, r2)
| view::transform(decomposed([](int, char x) { return x; }));
遗憾的是,似乎没有 transform-apply
视图。一个像其他答案一样的简单解决方案是调整您的 lambda,以便使用 std::apply
(相当于 std::bind_front(std::apply<...>, your_lambda)
)
// C++20
template<typename F>
constexpr auto apply_to(F&& f) noexcept(noexcept([f=static_cast<F&&>(f)]{})) {
return [f=static_cast<F&&>(f)]<typename Tuple>(Tuple&& tuple) noexcept(noexcept(::std::apply(f, static_cast<Tuple&&>(tuple)))) -> decltype(auto) {
return ::std::apply(f, static_cast<Tuple&&>(tuple));
};
}
// C++17
// (Or C++14 with another std::apply implementation, like ranges::tuple_apply)
template<typename F>
constexpr auto apply_to(F&& f) {
return [f=static_cast<F&&>(f)](auto&& tuple) noexcept(noexcept(::std::apply(f, static_cast<decltype(tuple)&&>(tuple)))) -> decltype(auto) {
return ::std::apply(f, static_cast<decltype(tuple)&&>(tuple));
};
}
并将您的 lambda 包装为 apply_to([](int a, char x) { /*...*/ })
。
或者,结构化绑定非常短(在 C++17 中)
// Be careful about excessive copying. Fine for the simple
// `std::tuple<char, int>`, but consider forwarding references
auto chars = view::zip(r1, r2) | view::transform([](auto zipped) {
auto [a, x] = zipped;
return x;
});