web-dev-qa-db-ja.com

スイッチを使用せずに実行時にタプルにインデックスを付けるC ++ 11の方法

以下のようなc ++ 11コードがあります。

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

この大きなスイッチを削除する方法はありますか?ご了承ください get<var>は、varが定数ではないため機能しませんが、varが狭い範囲(0-20)にあることはわかっています。

ここでのポイントは、配列ルックアップを引き起こす配列の使用を避けることです...

編集:

パフォーマンスの問題については、議論があります ifおよびswitchステートメントに対する関数の配列のパフォーマンス

私自身の目的のために、どちらが良いかについては議論しません。

31
w00d

以下は、インデックスシーケンスを使用しないバージョンです。

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 [〜#〜]

28
Oktalist

これは、再帰のない、読みにくいオーバージェネリックな実装です。これを本番環境で使用することはないと思います。これは書き込み専用コードの良い例ですが、実行できるのは興味深いことです。 ( [〜#〜] 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);
    }
  }
}
16
Casey

それは可能ですが、かなり醜いです:

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

case特殊化のテンプレート引数リストに、元のswitchSwitchラベルであった各定数をリストするだけです。

これをコンパイルするには、N特殊化の引数リスト内のすべてのSwitchに対してdoSomething(std::get<N>(t))が有効な式である必要があります...しかし、switchステートメントも。

少数のケースで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);
_

これにより_Switch<0,1,2,3>_が作成されるため、_0_のケースが必要ない場合は、_index_sequence_を操作する必要があります。これにより、リストの先頭からゼロが切り取られます。

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

残念ながら、GCCは_make_index_sequence<255>_をコンパイルしようとするとクラッシュします。再帰が多すぎてメモリが多すぎるためです。また、Clangはデフォルトで拒否します(_-ftemplate-instantiation-depth_のデフォルトが非常に低いため)これはそうではありません非常に実用的なソリューションです!

9
Jonathan Wakely

Oktalistの回答を少し堅牢にするために変更しました。

  • make visit_at method constexpr
  • 訪問者が任意の数の引数を渡すことを許可します(訪問されたTuple要素は引き続き最初のパラメーターが必要です)
  • 訪問者が値を返すことを許可する
  • visit_atメソッドをstd::get互換タイプ(std::arrayなど)と互換性があるようにする

完全を期すために、私は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 [〜#〜](デモはC++ 11ではありません(遅延のため))、しかし、上記の実装はする必要があります)

2
monkey0506

私はこのスレッドがかなり古いことを知っていますが、コードベースの静的なディスパッチを介して仮想ディスパッチを置き換えようとして、偶然見つけました。

これまでに紹介したすべてのソリューションとは対照的に、これは線形検索ではなくバイナリ検索を使用するため、私の理解では、O(log(n))ではなくO(n)になるはずです。それ以外は、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);
}
_

このソリューションでは、_visit_impl_の呼び出し回数は_7_です。線形検索アプローチでは、代わりに_58_になります。

提示された別の興味深い解決策は、 ここにO(1)アクセスを提供することもできます。ただし、サイズがO(n)の関数マップが生成されるため、より多くのストレージが犠牲になります。

0
MechaTheo