C++11 way to index tuple at runtime without using switch

后端 未结 7 1094
[愿得一人]
[愿得一人] 2020-12-02 10:45

I have a piece of c++11 code similar like below:

switch(var) {
   case 1: dosomething(std::get<1>(tuple));
   case 2: dosomething(std::get<2>(tup         


        
相关标签:
7条回答
  • 2020-12-02 11:07

    It's possible but it's pretty ugly:

    #include <tuple>
    #include <iostream>
    
    template<typename T>
    void doSomething(T t) { std::cout << t << '\n';}
    
    template<int... N>
    struct Switch;
    
    template<int N, int... Ns>
    struct Switch<N, Ns...>
    {
      template<typename... T>
        void operator()(int n, std::tuple<T...>& t)
        {
          if (n == N)
            doSomething(std::get<N>(t));
          else
            Switch<Ns...>()(n, t);
        }
    };
    
    // default
    template<>
    struct Switch<>
    {
      template<typename... T>
        void operator()(int n, std::tuple<T...>& t) { }
    };
    
    int main()
    {
      std::tuple<int, char, double, int, int, const char*> t;
      Switch<1, 2, 4, 5>()(4, t);
    }
    

    Just list each constant that would have been a case label in the original switch in the template argument list for the Switch specialization.

    For this to compile, doSomething(std::get<N>(t)) must be a valid expression for every N in the argument list of the Switch specialization ... but that's true of the switch statement too.

    For a small number of cases it compiles to the same code as a switch, I didn't check if it scales to large numbers of cases.

    If you don't want to type out every number in Switch<1, 2, 3, 4, ... 255> then you could create a std::integer_sequence and then use that to instantiate the Switch:

    template<size_t... N>
    Switch<N...>
    make_switch(std::index_sequence<N...>)
    {
      return {};
    }
    
    std::tuple<int, char, double, int, int, const char*> t;
    make_switch(std::make_index_sequence<4>{})(3, t);
    

    This creates a Switch<0,1,2,3> so if you don't want the 0 case you'd need to manipulate the index_sequence, e.g. this chops the zero off the front of the list:

    template<size_t... N>
    Switch<N...>
    make_switch(std::index_sequence<0, N...>)
    {
      return {};
    }
    

    Unfortunately GCC crashes when trying to compile make_index_sequence<255> as it involves too much recursion and uses too much memory, and Clang rejects it by default too (because it has a very low default for -ftemplate-instantiation-depth) so this isn't a very practical solution!

    0 讨论(0)
  • 2020-12-02 11:09

    I modified Oktalist's answer to make it slightly more robust:

    • make visit_at method constexpr
    • allow visitor to pass any number of arguments (visited tuple element is still required first parameter)
    • allow visitor to return a value
    • make visit_at method compatible with any std::get-compatible type (e.g., std::array)

    For completeness sake, I made it noexcept as well, though that is a mess (where is noexcept(auto) already?).

    namespace detail
    {
        template<std::size_t I>
        struct visit_impl
        {
            template<typename Tuple, typename F, typename ...Args>
            inline static constexpr int visit(Tuple const &tuple, std::size_t idx, F fun, Args &&...args) noexcept(noexcept(fun(std::get<I - 1U>(tuple), std::forward<Args>(args)...)) && noexcept(visit_impl<I - 1U>::visit(tuple, idx, fun, std::forward<Args>(args)...)))
            {
                return (idx == (I - 1U) ? (fun(std::get<I - 1U>(tuple), std::forward<Args>(args)...), void(), 0) : visit_impl<I - 1U>::visit(tuple, idx, fun, std::forward<Args>(args)...));
            }
    
            template<typename R, typename Tuple, typename F, typename ...Args>
            inline static constexpr R visit(Tuple const &tuple, std::size_t idx, F fun, Args &&...args) noexcept(noexcept(fun(std::get<I - 1U>(tuple), std::forward<Args>(args)...)) && noexcept(visit_impl<I - 1U>::template visit<R>(tuple, idx, fun, std::forward<Args>(args)...)))
            {
                return (idx == (I - 1U) ? fun(std::get<I - 1U>(tuple), std::forward<Args>(args)...) : visit_impl<I - 1U>::template visit<R>(tuple, idx, fun, std::forward<Args>(args)...));
            }
        };
    
        template<>
        struct visit_impl<0U>
        {
            template<typename Tuple, typename F, typename ...Args>
            inline static constexpr int visit(Tuple const&, std::size_t, F, Args&&...) noexcept
            {
                return 0;
            }
    
            template<typename R, typename Tuple, typename F, typename ...Args>
            inline static constexpr R visit(Tuple const&, std::size_t, F, Args&&...) noexcept(noexcept(R{}))
            {
                static_assert(std::is_default_constructible<R>::value, "Explicit return type of visit_at method must be default-constructible");
                return R{};
            }
        };
    }
    
    template<typename Tuple, typename F, typename ...Args>
    inline constexpr void visit_at(Tuple const &tuple, std::size_t idx, F fun, Args &&...args) noexcept(noexcept(detail::visit_impl<std::tuple_size<Tuple>::value>::visit(tuple, idx, fun, std::forward<Args>(args)...)))
    {
        detail::visit_impl<std::tuple_size<Tuple>::value>::visit(tuple, idx, fun, std::forward<Args>(args)...);
    }
    
    template<typename R, typename Tuple, typename F, typename ...Args>
    inline constexpr R visit_at(Tuple const &tuple, std::size_t idx, F fun, Args &&...args) noexcept(noexcept(detail::visit_impl<std::tuple_size<Tuple>::value>::template visit<R>(tuple, idx, fun, std::forward<Args>(args)...)))
    {
        return detail::visit_impl<std::tuple_size<Tuple>::value>::template visit<R>(tuple, idx, fun, std::forward<Args>(args)...);
    }
    

    DEMO (demo is not C++11 (due to laziness), but the implementation above should be)

    0 讨论(0)
  • 2020-12-02 11:10

    Here's a version that doesn't use an index sequence:

    template <size_t I>
    struct visit_impl
    {
        template <typename T, typename F>
        static void visit(T& tup, size_t idx, F fun)
        {
            if (idx == I - 1) fun(std::get<I - 1>(tup));
            else visit_impl<I - 1>::visit(tup, idx, fun);
        }
    };
    
    template <>
    struct visit_impl<0>
    {
        template <typename T, typename F>
        static void visit(T& tup, size_t idx, F fun) { assert(false); }
    };
    
    template <typename F, typename... Ts>
    void visit_at(std::tuple<Ts...> const& tup, size_t idx, F fun)
    {
        visit_impl<sizeof...(Ts)>::visit(tup, idx, fun);
    }
    
    template <typename F, typename... Ts>
    void visit_at(std::tuple<Ts...>& tup, size_t idx, F fun)
    {
        visit_impl<sizeof...(Ts)>::visit(tup, idx, fun);
    }
    

    DEMO

    0 讨论(0)
  • 2020-12-02 11:11

    For c++11 here is a concise approach that returns a pointer:

    template <typename Tuple, long template_index = std::tuple_size<Tuple>::value>
    struct tuple_address {
      static void * of(Tuple & tuple, long function_index) {
        if (template_index - 1 == function_index) {
          return &std::get<template_index - 1>(tuple);
        } else {
          return tuple_address<Tuple, template_index - 1>::of(tuple, function_index);
        }
      }
    };
    template <typename Tuple>
    struct tuple_address<Tuple, 0> {
      static void * of(Tuple & tuple, long function_index) {
        return 0;
      }
    };
    template <typename Tuple>
    void * tuple_address_of(Tuple & tuple, long index) {
      return tuple_address<Tuple>::of(tuple, index);
    }
    
    0 讨论(0)
  • 2020-12-02 11:16

    Here's an unreadably over-generic implementation without recursion. I don't think I'd use this in production - it's a good example of write-only code - but it's interesting that it can be done. (DEMO):

    #include <array>
    #include <cstddef>
    #include <initializer_list>
    #include <tuple>
    #include <iostream>
    #include <type_traits>
    #include <utility>
    
    template <std::size_t...Is> struct index_sequence {};
    
    template <std::size_t N, std::size_t...Is>
    struct build : public build<N - 1, N - 1, Is...> {};
    
    template <std::size_t...Is>
    struct build<0, Is...> {
        using type = index_sequence<Is...>;
    };
    
    template <std::size_t N>
    using make_index_sequence = typename build<N>::type;
    
    template <typename T>
    using remove_reference_t = typename std::remove_reference<T>::type;
    
    namespace detail {
    template <class Tuple, class F, std::size_t...Is>
    void tuple_switch(const std::size_t i, Tuple&& t, F&& f, index_sequence<Is...>) {
      [](...){}(
        (i == Is && (
           (void)std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(t))), false))...
      );
    }
    } // namespace detail
    
    template <class Tuple, class F>
    void tuple_switch(const std::size_t i, Tuple&& t, F&& f) {
      static constexpr auto N =
        std::tuple_size<remove_reference_t<Tuple>>::value;
    
      detail::tuple_switch(i, std::forward<Tuple>(t), std::forward<F>(f),
                           make_index_sequence<N>{});
    }
    
    constexpr struct {
      template <typename T>
      void operator()(const T& t) const {
          std::cout << t << '\n';
      }
    } print{};
    
    int main() {
    
      {
        auto const t = std::make_tuple(42, 'z', 3.14, 13, 0, "Hello, World!");
    
        for (std::size_t i = 0; i < std::tuple_size<decltype(t)>::value; ++i) {
          tuple_switch(i, t, print);
        }
      }
    
      std::cout << '\n';
    
      {
        auto const t = std::array<int, 4>{{0,1,2,3}};
        for (std::size_t i = 0; i < t.size(); ++i) {
          tuple_switch(i, t, print);
        }
      }
    }
    
    0 讨论(0)
  • 2020-12-02 11:24

    No need to get all cray cray in c++17.

    template <class Func, class Tuple, size_t N = 0>
    inline void runtime_get(Func func, Tuple& tup, size_t idx) {
        if (N == idx) {
            std::invoke(func, std::get<N>(tup));
            return;
        }
    
        if constexpr (N + 1 < std::tuple_size_v<Tuple>) {
            return runtime_get<Func, Tuple, N + 1>(func, tup, idx);
        }
    }
    

    And runtime tuple_element for fun.

    // Returns a pointer to the type, so the element is not initialized.
    template <class Tuple, class Func, size_t N = 0>
    inline void runtime_tuple_element(Func func, size_t idx) {
        if (N == idx) {
            std::tuple_element_t<N, Tuple>* ptr = nullptr;
            std::invoke(func, ptr);
            return;
        }
    
        if constexpr (N + 1 < std::tuple_size_v<Tuple>) {
            return runtime_tuple_element<Tuple, Func, N + 1>(func, idx);
        }
    }
    
    0 讨论(0)
提交回复
热议问题