C++11 在运行时不使用 switch 索引元组的方法
C++11 way to index tuple at runtime without using switch
我有一段类似于下面的 C++11 代码:
switch(var) {
case 1: dosomething(std::get<1>(tuple));
case 2: dosomething(std::get<2>(tuple));
...
}
有什么办法可以去掉这个大开关吗?请注意,get<var>
不起作用,因为 var 不是常数,但我知道 var 在小范围内,即 (0-20)。
注意这里的重点是避免使用导致数组查找的数组...
编辑:
好的关于性能的问题,有讨论
Performance of array of functions over if and switch statements
为了我自己的目的,我不争论哪个更好。
这是可能的,但它很丑陋:
#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);
}
只需在 Switch
专业化的模板参数列表中列出原本 switch
中的每个常量 case
标签。
为了编译,doSomething(std::get<N>(t))
必须是 Switch
专业化参数列表中每个 N
的有效表达式...但 [=14] 是这样=] 也声明。
对于少数情况,它编译为与 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
的默认值非常低) ) 所以这不是一个非常实用的解决方案!
这是一个不使用索引序列的版本:
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);
}
}
}
我修改了 Oktalist 的答案,使其更加稳健:
- 制作
visit_at
方法constexpr
- 允许访问者传递任意数量的参数(访问的元组元素仍然需要第一个参数)
- 允许访问者return一个值
- 使
visit_at
方法与任何 std::get
兼容的类型兼容(例如,std::array
)
为了完整起见,我也做了 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(demo不是C++11(懒惰),但上面的实现应该是)
我知道这个线程很旧,但我在尝试通过代码库中的静态调度替换虚拟调度时偶然发现了它。
与迄今为止提出的所有解决方案相比,这个解决方案使用二进制搜索而不是线性搜索,所以在我的理解中它应该是 O(log(n))
而不是 O(n)
。除此之外,它只是
的修改版本
#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
。
提出的另一个有趣的解决方案 here 甚至设法提供 O(1)
访问权限。然而,以更多存储为代价,因为生成了大小为 O(n)
的函数映射。
无需在 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);
}
}
和运行时 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);
}
}
对于 c++11,这是一个简洁的方法,returns 一个指针:
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 非递归
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 {});
}
使用:
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 << ' ';
});
}
输出:
i1 d2.5 i3 cZ
这是没有 compile-time 递归(这很糟糕,因为它会降低编译时间)并且没有 switch-case:
的 C++17 解决方案
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);
}
这里有几个注意事项:
您可以在 C++11 中获得相同的结果,但您应该使用 well-known 'hack' 和本地数组而不是使用 C++17 fold-expressions通过在数组的初始化列表中展开一个包)。类似于:
std::array<bool, sizeof...(Ts)> arr = { ((Is == idx && (pred(std::get<Is>(tpl)), true)), ...) };
我们在 Is...
pack-expansion 和 pred
执行中使用 comma-operator,即 comma-operator 的所有操作数将是执行,整个 comma-expression 的结果是最后一个操作数的结果。
我们将每个操作数强制转换为 void
以使编译器静音(unused expression value
或类似的东西)
我有一段类似于下面的 C++11 代码:
switch(var) {
case 1: dosomething(std::get<1>(tuple));
case 2: dosomething(std::get<2>(tuple));
...
}
有什么办法可以去掉这个大开关吗?请注意,get<var>
不起作用,因为 var 不是常数,但我知道 var 在小范围内,即 (0-20)。
注意这里的重点是避免使用导致数组查找的数组...
编辑:
好的关于性能的问题,有讨论 Performance of array of functions over if and switch statements
为了我自己的目的,我不争论哪个更好。
这是可能的,但它很丑陋:
#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);
}
只需在 Switch
专业化的模板参数列表中列出原本 switch
中的每个常量 case
标签。
为了编译,doSomething(std::get<N>(t))
必须是 Switch
专业化参数列表中每个 N
的有效表达式...但 [=14] 是这样=] 也声明。
对于少数情况,它编译为与 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
的默认值非常低) ) 所以这不是一个非常实用的解决方案!
这是一个不使用索引序列的版本:
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);
}
}
}
我修改了 Oktalist 的答案,使其更加稳健:
- 制作
visit_at
方法constexpr
- 允许访问者传递任意数量的参数(访问的元组元素仍然需要第一个参数)
- 允许访问者return一个值
- 使
visit_at
方法与任何std::get
兼容的类型兼容(例如,std::array
)
为了完整起见,我也做了 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(demo不是C++11(懒惰),但上面的实现应该是)
我知道这个线程很旧,但我在尝试通过代码库中的静态调度替换虚拟调度时偶然发现了它。
与迄今为止提出的所有解决方案相比,这个解决方案使用二进制搜索而不是线性搜索,所以在我的理解中它应该是 O(log(n))
而不是 O(n)
。除此之外,它只是
#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
。
提出的另一个有趣的解决方案 here 甚至设法提供 O(1)
访问权限。然而,以更多存储为代价,因为生成了大小为 O(n)
的函数映射。
无需在 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);
}
}
和运行时 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);
}
}
对于 c++11,这是一个简洁的方法,returns 一个指针:
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 非递归
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 {});
}
使用:
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 << ' ';
});
}
输出: i1 d2.5 i3 cZ
这是没有 compile-time 递归(这很糟糕,因为它会降低编译时间)并且没有 switch-case:
的 C++17 解决方案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);
}
这里有几个注意事项:
您可以在 C++11 中获得相同的结果,但您应该使用 well-known 'hack' 和本地数组而不是使用 C++17 fold-expressions通过在数组的初始化列表中展开一个包)。类似于:
std::array<bool, sizeof...(Ts)> arr = { ((Is == idx && (pred(std::get<Is>(tpl)), true)), ...) };
我们在
Is...
pack-expansion 和pred
执行中使用 comma-operator,即 comma-operator 的所有操作数将是执行,整个 comma-expression 的结果是最后一个操作数的结果。我们将每个操作数强制转换为
void
以使编译器静音(unused expression value
或类似的东西)