以下のような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ステートメントに対する関数の配列のパフォーマンス
私自身の目的のために、どちらが良いかについては議論しません。
以下は、インデックスシーケンスを使用しないバージョンです。
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 [〜#〜] ):
#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);
}
}
}
それは可能ですが、かなり醜いです:
_#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
特殊化のテンプレート引数リストに、元のswitch
のSwitch
ラベルであった各定数をリストするだけです。
これをコンパイルするには、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
_のデフォルトが非常に低いため)これはそうではありません非常に実用的なソリューションです!
Oktalistの回答を少し堅牢にするために変更しました。
visit_at
method constexpr
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ではありません(遅延のため))、しかし、上記の実装はする必要があります)
私はこのスレッドがかなり古いことを知っていますが、コードベースの静的なディスパッチを介して仮想ディスパッチを置き換えようとして、偶然見つけました。
これまでに紹介したすべてのソリューションとは対照的に、これは線形検索ではなくバイナリ検索を使用するため、私の理解では、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)
の関数マップが生成されるため、より多くのストレージが犠牲になります。