С++ 11 способ индексирования кортежа во время выполнения без использования переключателя
У меня есть код С++ 11, похожий ниже:
switch(var) {
case 1: dosomething(std::get<1>(tuple));
case 2: dosomething(std::get<2>(tuple));
...
}
Есть ли способ удалить этот большой переключатель? Обратите внимание, что get<var>
не работает, потому что var не является константой, но я знаю, что var находится в малом диапазоне, то есть (0-20).
Обратите внимание, что здесь нужно избегать использования массива, который вызывает поиск массива...
EDIT:
хорошо по вопросу эффективности, есть дискуссия
Производительность массива функций над операторами if и switch
Для моей собственной цели я не утверждаю, какой из них лучше.
Ответы
Ответ 1
Здесь версия, которая не использует индексную последовательность:
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
Ответ 2
Здесь нечитабельно сверх-общая реализация без рекурсии. Я не думаю, что буду использовать это в производстве - это хороший пример кода с записью, но интересно, что это можно сделать. ( 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);
}
}
}
Ответ 3
Возможно, но это довольно уродливо:
#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
.
Для этого для компиляции doSomething(std::get<N>(t))
должно быть допустимым выражением для каждого N
в списке аргументов специализации switch
... но это верно и для оператора 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
), так что это не очень практичное решение!
Ответ 4
Я изменил ответ Oktalist, чтобы сделать его немного более устойчивым:
- сделать
visit_at
метод constexpr
- разрешить посетителю передавать любое количество аргументов (для элемента, которому требуется побывать, все еще требуется первый параметр)
- разрешить посетителю возвращать значение
- make
visit_at
метод, совместимый с любым std::get
-собираемым типом (например, std::array
)
Для полноты я тоже сделал это noexcept
, хотя это уже беспорядок (где noexcept (авто) уже.).
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 (демо не С++ 11 (из-за лени), но реализация выше должна быть)