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

w00*_*00d 31 c++ tuples c++11

我有一段类似下面的c ++ 11代码:

switch(var) {
   case 1: dosomething(std::get<1>(tuple));
   case 2: dosomething(std::get<2>(tuple));
   ...
}
Run Code Online (Sandbox Code Playgroud)

有没有办法删除这个大型交换机?请注意,get<var>这不起作用,因为var不是常量,但我知道var在小范围内,即(0-20).

请注意,这里的要点是避免使用导致数组查找的数组...

编辑:

在性能问题上,讨论 了if和switch语句的函数数组的性能

出于我自己的目的,我不认为哪一个更好.

Okt*_*ist 29

这是一个不使用索引序列的版本:

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);
}
Run Code Online (Sandbox Code Playgroud)

DEMO


Cas*_*sey 16

这是一个不可读的过度泛型实现,没有递归.我不认为我会在生产中使用它 - 这是只写代码的一个很好的例子 - 但有趣的是它可以完成.(演示):

#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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 很好用...让我们把它称为空的可变参数lambda pack扩展成语.我觉得不难读懂; 完美转发确定了可读性的最大成本. (2认同)

Jon*_*ely 9

这是可能的,但它很难看:

#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);
}
Run Code Online (Sandbox Code Playgroud)

只需在专门化的模板参数列表中列出case原始标签中的每个常量.switchSwitch

为了编译它, doSomething(std::get<N>(t))必须是专门化N的参数列表中的每一个的有效表达式Switch......但是对于该switch语句也是如此.

对于少数情况,它编译为与a相同的代码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);
Run Code Online (Sandbox Code Playgroud)

这样就创建了一个Switch<0,1,2,3>如果你不需要0你需要操作的情况index_sequence,例如这会从列表的前面剔除零:

template<size_t... N>
Switch<N...>
make_switch(std::index_sequence<0, N...>)
{
  return {};
}
Run Code Online (Sandbox Code Playgroud)

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


scx*_*scx 7

无需在 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

和运行时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);
    }
}
Run Code Online (Sandbox Code Playgroud)


Dmi*_*ich 7

这是没有编译时递归的 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);
}
Run Code Online (Sandbox Code Playgroud)

这里有几点注意事项:

  1. 您可以在 C++11 中获得相同的结果,但不应使用 C++17 折叠表达式,您应该对本地数组使用众所周知的“hack”(通过在数组的初始化列表中扩展包)。就像是:

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

  2. 我们使用逗号运算符进行Is...包扩展和pred执行,即逗号运算符的所有操作数都将被执行,整个逗号表达式的结果是最后一个操作数的结果。

  3. 我们将每个操作数转换为void以使编译器静音(unused expression value或类似的东西)