Search code examples
c++templatestemplate-meta-programmingstdtuple

Applying a function to tuple element only if that type is present in tuple


The thing I am trying to achieve is as follows, together with where i am right now:

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)>());
}

and implement this in a way that, for example:

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(); });
}

will return another std::tuple but an std::tuple<int, std::size_t, float> with elements 42 and 5 (length of "hello") and 3.14. If there is no element in the given tuple that is feasible to apply the given callable, just return the given tuple without doing anything. So, a copy of given std::tuple<int, std::string, float> will be returned in this latter case, or moved, whatever.

The problem I have is that compiler still sees the function being applied to other members in the tuple, in the ternary statement I have. How can I go around this? I need a compile-time ternary thing that would expand that make_tuple call correctly. Lastly, I need to get rid of that hardcoded std::string as well. I need to put in the argument type of callable there.

EDIT: Don't hesitate to use libraries like boost::hana if they are going to make the solution easier. Would be a nice exercise for me as well.


Solution

  • You can go via another intermediate template:

    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))...
                );
    }