简体   繁体   English

C++11 在运行时索引元组而不使用开关的方法

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

I have a piece of c++11 code similar like below:我有一段 c++11 代码类似如下:

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

Is there any way to remove this large switch?有什么办法可以去掉这个大开关吗? Note that get<var> does not work because var is not constant, but I know var is in small range ie (0-20).请注意, get<var>不起作用,因为 var 不是常量,但我知道 var 在小范围内,即 (0-20)。

Note that the point here is to avoid using an array that causes an array lookup...请注意,这里的重点是避免使用导致数组查找的数组......

EDIT:编辑:

well on the issue of performance, there is a discussion Performance of array of functions over if and switch statements关于性能问题,有一个讨论Performance of functions array over if and switch statements

For my own purpose, I do not argue which one is better.为了我自己的目的,我不争论哪个更好。

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演示

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

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.只需在Switch的模板参数列表中列出原始switch中的case标签的每个常量。

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.为了编译, doSomething(std::get<N>(t))必须是Switch的参数列表中每个N的有效表达式......但switch语句也是如此。

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.对于少数情况,它编译为与switch相同的代码,我没有检查它是否可以扩展到大量情况。

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 :如果您不想在Switch<1, 2, 3, 4, ... 255>输入每个数字,那么您可以创建一个std::integer_sequence然后使用它来实例化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 , eg this chops the zero off the front of the list:这将创建一个Switch<0,1,2,3>因此如果您不想要0情况,您需要操作index_sequence ,例如,这会从列表的前面砍掉零:

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!不幸的是 GCC 在尝试编译make_index_sequence<255>时崩溃,因为它涉及太多递归并使用太多内存,并且 Clang 默认也拒绝它(因为它的-ftemplate-instantiation-depth默认值非常低)所以这不是'一个非常实用的解决方案!

No need to get all cray cray in c++17.无需在 c++17 中获取所有 cray cray。

// Calls your func with tuple element.
template <class Func, class Tuple, size_t N = 0>
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.和运行时tuple_element好玩。

// Calls your func with a pointer to the type.
// Uses a pointer so the element is not initialized.
template <class Tuple, class Func, size_t N = 0>
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);
    }
}

Here is C++17 solution without compile-time recursion (which is bad because it degrades your compile time) and without switch-case:这是 C++17 解决方案,没有编译时递归(这很糟糕,因为它会降低编译时间)并且没有 switch-case:

template<typename TPred, typename ...Ts, size_t ...Is>
void invoke_at_impl(std::tuple<Ts...>& tpl, std::index_sequence<Is...>, size_t idx, TPred pred)
{
    ((void)(Is == idx && (pred(std::get<Is>(tpl)), true)), ...);
    // for example: std::tuple<int, float, bool> `transformations` (idx == 1):
    //
    // Is... expansion    -> ((void)(0 == idx && (pred(std::get<0>(tpl)), true)), (void)(1 == idx && (pred(std::get<1>(tpl)), true)), (void)(2 == idx && (pred(std::get<2>(tpl)), true)));
    //                    -> ((void)(false && (pred(std::get<0>(tpl)), true)), (void)(true && (pred(std::get<1>(tpl)), true)), (void)(false && (pred(std::get<2>(tpl)), true)));
    // '&&' short-circuit -> ((void)(false), (void)(true && (pred(std::get<1>(tpl)), true)), (void)(false), true)));
    //
    // i.e. pred(std::get<1>(tpl) will be executed ONLY for idx == 1
}

template<typename TPred, typename ...Ts>
void invoke_at(std::tuple<Ts...>& tpl, size_t idx, TPred pred)
{
    invoke_at_impl(tpl, std::make_index_sequence<sizeof...(Ts)>{}, idx, pred);
}

Several notes here:这里有几个注意事项:

  1. You CAN achieve same result in C++11 but instead of using C++17 fold-expressions you should use well-known 'hack' with local array (by expanding a pack inside array's initialization list).您可以在 C++11 中获得相同的结果,但不应使用 C++17 折叠表达式,您应该对本地数组使用众所周知的“hack”(通过在数组的初始化列表中扩展一个包)。 Something like:就像是:

    std::array<bool, sizeof...(Ts)> arr = { ((Is == idx && (pred(std::get<Is>(tpl)), true)), ...) };

  2. We are using comma-operator for both Is... pack-expansion AND pred execution, ie all operands of the comma-operator will be executed and the result of the whole comma-expression is the result of the last operand.我们对Is... pack-expansion 和pred执行都使用逗号运算符,即逗号运算符的所有操作数都将被执行,并且整个逗号表达式的结果是最后一个操作数的结果。

  3. We are casting each operand to void in order to silence compiler ( unused expression value or something like this)我们将每个操作数转换为void以使编译器静音( unused expression value或类似的东西)

I modified Oktalist's answer to make it slightly more robust:我修改了 Oktalist 的答案,使其更加强大:

  • make visit_at method constexpr使visit_at方法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 (eg, std::array )使visit_at方法与任何std::get兼容类型(例如std::array )兼容

For completeness sake, I made it noexcept as well, though that is a mess (where is noexcept(auto) already?).为了完整起见,我也将其noexcept ,尽管这很混乱( noexcept(auto)已经在哪里?)。

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) DEMO (demo不是C++11(因为懒),但是上面的实现应该是)

I know this thread is quite old, but i stumbled across it in my attempt to replace a virtual dispatch through a static one in my code base.我知道这个线程已经很老了,但是我在尝试通过代码库中的静态调度替换虚拟调度时偶然发现了它。

In contrast to all solutions presented so far this one uses a binary search instead of a linear search, so in my understanding it should be O(log(n)) instead of O(n) .与迄今为止提出的所有解决方案相比,这个解决方案使用二分搜索而不是线性搜索,所以在我看来,它应该是O(log(n))而不是O(n) Other than that it is just a modified version of the solution presented by Oktalist除此之外,它只是Oktalist 提出解决方案的修改版本

#include <tuple>
#include <cassert>

template <std::size_t L, std::size_t U>
struct visit_impl
{
    template <typename T, typename F>
    static void visit(T& tup, std::size_t idx, F fun)
    {
        static constexpr std::size_t MEDIAN = (U - L) / 2 + L;
        if (idx > MEDIAN)
            visit_impl<MEDIAN, U>::visit(tup, idx, fun);
        else if (idx < MEDIAN)
            visit_impl<L, MEDIAN>::visit(tup, idx, fun);
        else
            fun(std::get<MEDIAN>(tup));
    }
};

template <typename F, typename... Ts>
void visit_at(const std::tuple<Ts...>& tup, std::size_t idx, F fun)
{
    assert(idx <= sizeof...(Ts));
    visit_impl<0, sizeof...(Ts)>::visit(tup, idx, fun);
}

template <typename F, typename... Ts>
void visit_at(std::tuple<Ts...>& tup, std::size_t idx, F fun)
{
    assert(idx <= sizeof...(Ts));
    visit_impl<0, sizeof...(Ts)>::visit(tup, idx, fun);
}

/* example code */

/* dummy template to generate different callbacks */
template <int N>
struct Callback
{
    int Call() const
    {
        return N;
    }
};

template <typename T>
struct CallbackTupleImpl;

template <std::size_t... Indx>
struct CallbackTupleImpl<std::index_sequence<Indx...>>
{
    using type = std::tuple<Callback<Indx>...>;
};

template <std::size_t N>
using CallbackTuple = typename CallbackTupleImpl<std::make_index_sequence<N>>::type;

int main()
{
    CallbackTuple<100> myTuple;
    int value{};
    visit_at(myTuple, 42, [&value](auto& pc) { value = pc.Call(); });
    assert(value == 42);
}

With this solution the number of calls to visit_impl is 7 .使用此解决方案,对visit_impl的调用visit_impl7 With the linear search approach it would be 58 instead.使用线性搜索方法,它将改为58

Another interesting solution presented here even manages to provide O(1) access. 这里提出的另一个有趣的解决方案甚至设法提供O(1)访问。 However at the cost of more storage, since a function map with the size O(n) is generated.但是以更多存储为代价,因为生成了大小为O(n)的函数映射。

For c++11 here is a concise approach that returns a pointer:对于 c++11,这里是一种返回指针的简洁方法:

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

C++17 non-recursive C++17 非递归

template <typename T>
inline constexpr size_t tuple_size_v = std::tuple_size<T>::value;

template <typename T, typename F, std::size_t... I>
constexpr void visit_impl(T& tup, const size_t idx, F fun, std::index_sequence<I...>)
{
    assert(idx < tuple_size_v<T>);
    ((I == idx ? fun(std::get<I>(tup)) : void()), ...);
}

template <typename F, typename... Ts, typename Indices = std::make_index_sequence<sizeof...(Ts)>>
constexpr void visit_at(std::tuple<Ts...>& tup, const size_t idx, F fun)
{
    visit_impl(tup, idx, fun, Indices {});
}

template <typename F, typename... Ts, typename Indices = std::make_index_sequence<sizeof...(Ts)>>
constexpr void visit_at(const std::tuple<Ts...>& tup, const size_t idx, F fun)
{
    visit_impl(tup, idx, fun, Indices {});
}

Using:使用:

auto tuple = std::tuple { 1, 2.5, 3, 'Z' };
// print it to cout
for (size_t i = 0; i < tuple_size_v<decltype(tuple)>; ++i) {
    visit_at(tuple, i, [](auto&& arg) {
        using T = std::decay_t<decltype(arg)>;
        std::cout << *typeid(T).name() << arg << ' ';
    });
}

Output: i1 d2.5 i3 cZ输出: i1 d2.5 i3 cZ

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM