仅当元组中存在该类型时,才将函数应用于元组元素

时间:2019-06-27 17:13:59

标签: c++ templates template-meta-programming stdtuple

我要实现的目标以及现在的位置如下:

template <typename Fn, typename Tuple, size_t... Is>
auto apply_if_impl(Tuple t, Fn&& f, std::index_sequence<Is...>) {
    return std::make_tuple(
        std::is_same_v<std::string, std::tuple_element_t<Is, Tuple>> ? 
        f(std::get<Is>(t)) : 
        std::get<Is>(t)...
    );
}

template <typename Fn, typename ...Ts>
auto apply_if(std::tuple<Ts...> t, Fn&& f) {
    return apply_if_impl(t, f, std::make_index_sequence<sizeof...(Ts)>());
}

并以例如以下方式实现:

int main() {
    std::tuple<int, std::string, float> t{42, "hello", 3.14f};

    // this one should return 
    // std::tuple<int, std::size_t, float>{42, 5, 3.14f};
    apply_if(t, [](std::string s){ return s.size(); });

    // return value of this should be equal to t above since
    // there is no vector element in the given tuple
    apply_if(t, [](std::vector<int> s){ return s.size(); });
}

将返回另一个std::tuple,但返回一个std::tuple<int, std::size_t, float>,其元素为42和5(长度为"hello")和3.14。如果给定元组中没有可应用给定可调用对象的元素,则只需不执行任何操作就返回给定元组。因此,给定std::tuple<int, std::string, float>的副本 在后一种情况下将被退回,或被移走。

我遇到的问题是,在我拥有的三元语句中,编译器仍会看到该函数应用于元组中的其他成员。我该如何解决?我需要一个编译时的三元函数,以正确扩展make_tuple调用。最后,我还需要除去该硬编码的std::string。我需要在其中放入可调用的参数类型。

编辑:如果要简化解决方案,请立即使用boost::hana之类的库。对我来说也是很好的锻炼。

2 个答案:

答案 0 :(得分:3)

您可以通过另一个中间模板:

template <bool>
class Select
{
public:
    template <typename F, typename T>
    T& operator()(F&, T& t) const
    {
        return t;
    }
};

template <>
class Select<true>
{
public:
    template <typename F, typename T>
    auto operator()(F& f, T& t) const -> decltype(f(t))
    {
        return f(t);
    }
};

template<typename Fn, typename Tuple, size_t ... Is>
auto apply_if_impl(Tuple t, Fn&& f, std::index_sequence<Is...>)
{
    return std::make_tuple
            (
                    Select<std::is_same_v<std::string, std::tuple_element_t<Is, Tuple>>>()
                           (f, std::get<Is>(t))...
            );
}

答案 1 :(得分:3)

C ++ 17解决方案:

template<class Fn, typename Arg>
decltype(auto) apply_if_invocable(Fn fn, Arg&& arg) {
    if constexpr (std::is_invocable_v<Fn, Arg>)
        return fn(std::forward<Arg>(arg));
    else
        return std::forward<Arg>(arg);
}

template <typename Tuple, class Fn, size_t... Is>
auto apply_if_impl(Tuple&& t, Fn fn, std::index_sequence<Is...>) {
    return std::make_tuple(apply_if_invocable(fn, std::get<Is>(std::forward<Tuple>(t)))...);
}

template <class Tuple, class Fn>
auto apply_if(Tuple&& t, Fn fn) {
    return apply_if_impl(std::forward<Tuple>(t), fn, 
        std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}

然后:

struct Fn {
    int operator()(int v) {
        return 0;
    }

    std::size_t operator()(const std::string& v) {
        return v.length();
    }    
};

std::tuple<int, std::string, std::unique_ptr<int>> t{
    42, "hello", std::make_unique<int>(21)};
auto z = apply_if(std::move(t), Fn{});

assert(std::get<0>(z) == 0);
assert(std::get<1>(z) == 5);
assert(*std::get<2>(z) == 21);