将函数应用于元组的每个元素

Applying a function to each element of a tuple

本文关键字:元素 元组 函数 应用于      更新时间:2023-10-16

给定一个类似std::tuple的对象(即具有定义的tuple_sizeget语义)和一个一元函子对象ftor,我希望能够在类tuple对象的每个元素上调用ftor

如果我忽略返回值,我知道 int 数组技巧:

namespace details {
template <typename Ftor, typename Tuple, size_t... Is>
void apply_unary(Ftor&& ftor, Tuple&& tuple, std::index_sequence<Is...>) {
    using std::get;
    int arr[] = { (ftor(get<Is>(std::forward<Tuple>(tuple))), void(), 0)... };
}
} // namespace details
template <typename Ftor, typename Tuple>
void apply_unary(Ftor&& ftor, Tuple&& tuple) {
    details::apply_unary(std::forward<Ftor>(ftor),
                         std::forward<Tuple>(tuple),
                         std::make_index_sequence<std::tuple_size<Tuple>::value> {});
}

如果我想要返回值,我可以将int []技巧替换为调用 std::make_tuple 并返回它。前提是对ftor对象的调用都没有void返回值,这将起作用......

因此,我的问题是:考虑到我想获取呼叫的结果,如何处理可能返回void的呼叫?

唯一的要求是我应该以元组的形式获取结果,并能够分辨出哪个调用导致所述结果元组的哪个元素。

正如 @Jarod42 所建议的,用一个额外的层来包装调用,该层负责用虚拟结构替换 void 返回将解决问题:

struct no_return {};
namespace details {
template <typename Ftor, typename Arg>
auto call(Ftor&& ftor, Arg&& arg)
    -> std::enable_if_t<std::is_void<decltype(std::forward<Ftor>(ftor)(std::forward<Arg>(arg)))>::value, no_return> {
    std::forward<Ftor>(ftor)(std::forward<Arg>(arg));
    return no_return {};
}
template <typename Ftor, typename Arg>
auto call(Ftor&& ftor, Arg&& arg)
    -> std::enable_if_t<!std::is_void<decltype(std::forward<Ftor>(ftor)(std::forward<Arg>(arg)))>::value, decltype(std::forward<Ftor>(ftor)(std::forward<Arg>(arg)))> {
    return std::forward<Ftor>(ftor)(std::forward<Arg>(arg));
}
template <typename Ftor, typename Tuple, size_t... Is>
auto apply_unary(Ftor&& ftor, Tuple&& tuple, std::index_sequence<Is...>) {
    using std::get;
    return std::tuple<decltype(call(ftor, get<Is>(std::forward<Tuple>(tuple))))...> { call(ftor, get<Is>(std::forward<Tuple>(tuple)))... } ;
}
} // namespace details
template <typename Ftor, typename Tuple>
auto apply_unary(Ftor&& ftor, Tuple&& tuple) {
    return details::apply_unary(std::forward<Ftor>(ftor),
                                std::forward<Tuple>(tuple),
                                std::make_index_sequence<std::tuple_size<std::decay_t<Tuple> >::value> {});
}

现场演示可在 Coliru 上获得

我使用 SFINAE 来区分两个重载。它看起来有点丑,所以如果你有任何改进建议......我都听见了!

另一种方式:

namespace details {
struct apply_unary_helper_t {};
template<class T> 
T&& operator,(T&& t, apply_unary_helper_t) { // Keep the non-void result.
    return std::forward<T>(t); 
}
template <typename Ftor, typename Tuple, size_t... Is>
void apply_unary(Ftor&& ftor, Tuple&& tuple, std::index_sequence<Is...>) {
    auto r = {(ftor(std::get<Is>(std::forward<Tuple>(tuple))), apply_unary_helper_t{})...};
    static_cast<void>(r); // Suppress unused variable warning.
}
} // namespace details
template <typename Ftor, typename Tuple>
void apply_unary(Ftor&& ftor, Tuple&& tuple) {
    details::apply_unary(std::forward<Ftor>(ftor),
                         std::forward<Tuple>(tuple),
                         std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tuple>>::value> {});
}

在上面,它operator,适用于ftorapply_unary_helper_t的结果。如果ftor的结果是void,那么rstd::initializer_list<details::apply_unary_helper_t>的,否则r是你可以利用的std::initializer_list<decltype(ftor(...))>