如何反转可变参数模板函数的参数顺序
How to reverse the order of arguments of a variadic template function?
我有一个模板函数与可变参数模板参数,像这样
template<typename Args...>
void ascendingPrint(Args... args) { /* ... */ }
我想写
template<typename Args...>
void descendingPrint(Args... args) {
/* implementation using ascendingPrint()? */
}
如何在传递参数包 args
之前反转参数包的顺序,即在伪代码中:
template<typename Args...>
void descendingPrint(Args... args) {
ascendingPrint( reverse(args) );
}
总体方法和用法
总体方法包括将参数打包到引用的std::tuple
中,利用std::forward_as_tuple()
的完美转发机制。
这意味着,在运行时,您应该产生非常小的开销,并且没有不必要的复制/移动操作。此外,该框架不使用递归(除了编译时递归,这对于生成索引是不可避免的),因此即使在编译器无法内联递归函数调用的情况下,也没有运行时开销的风险(无论如何都不太可能,所以这更像是一个学术论点)。
此外,此解决方案是通用的,因为您可以将其用作仅头文件库,以最小的努力使用反向参数调用函数:descending_print()
应该只是ascending_print()
周围的最小的薄包装。
应该是这样的:
MAKE_REVERT_CALLABLE(ascending_print)
template<typename... Args>
void descending_print(Args&&... args)
{
revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...);
}
下面是实现的演示。
第一步:恢复类型序列
下面是恢复类型序列的简单方法:
#include <tuple>
#include <type_traits>
template<typename, typename>
struct append_to_type_seq { };
template<typename T, typename... Ts>
struct append_to_type_seq<T, std::tuple<Ts...>>
{
using type = std::tuple<Ts..., T>;
};
template<typename... Ts>
struct revert_type_seq
{
using type = std::tuple<>;
};
template<typename T, typename... Ts>
struct revert_type_seq<T, Ts...>
{
using type = typename append_to_type_seq<
T,
typename revert_type_seq<Ts...>::type
>::type;
};
一个小测试程序:
int main()
{
static_assert(
std::is_same<
revert_type_seq<char, int, bool>::type,
std::tuple<bool, int, char>
>::value,
"Error"
);
}
和一个实例。
第二步:还原元组
下一步包括恢复元组。给定通常的索引欺骗机制:
template <int... Is>
struct index_list { };
namespace detail
{
template <int MIN, int N, int... Is>
struct range_builder;
template <int MIN, int... Is>
struct range_builder<MIN, MIN, Is...>
{
typedef index_list<Is...> type;
};
template <int MIN, int N, int... Is>
struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
{ };
}
template<int MIN, int MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
与上面定义的函数一起,元组可以很容易地以这种方式恢复:
template<typename... Args, int... Is>
typename revert_type_seq<Args...>::type
revert_tuple(std::tuple<Args...> t, index_list<Is...>)
{
using reverted_tuple = typename revert_type_seq<Args...>::type;
// Forwarding machinery that handles both lvalues and rvalues...
auto rt = std::forward_as_tuple(
std::forward<
typename std::conditional<
std::is_lvalue_reference<
typename std::tuple_element<Is, reverted_tuple>::type
>::value,
typename std::tuple_element<Is, reverted_tuple>::type,
typename std::remove_reference<
typename std::tuple_element<Is, reverted_tuple>::type
>::type
>::type
>(std::get<sizeof...(Args) - Is - 1>(t))...
);
return rt;
}
template<typename... Args>
typename revert_type_seq<Args...>::type
revert_tuple(std::tuple<Args...> t)
{
return revert_tuple(t, index_range<0, sizeof...(Args)>());
}
下面是一个简单的测试程序:
#include <iostream>
int main()
{
std::tuple<int, int, char> t(42, 1729, 'c');
auto rt = revert_tuple(t);
std::cout << std::get<0>(rt) << " "; // Prints c
std::cout << std::get<1>(rt) << " "; // Prints 1729
std::cout << std::get<2>(rt) << " "; // Prints 42
}
下面是一个实例。
第三步:返回函数的实参
最后一步是在调用目标函数时解包元组。下面是另一个通用实用程序,可以节省几行代码:
template<typename... Args>
typename revert_type_seq<Args...>::type
make_revert(Args&&... args)
{
auto t = std::forward_as_tuple(std::forward<Args>(args)...);
return revert_tuple(t);
}
上面的函数创建了一个元组,其元素是所提供的实参,但顺序相反。我们还没有准备好定义目标:
template<typename T>
void ascending_print(T&& t)
{
std::cout << std::forward<T>(t) << " ";
}
template<typename T, typename... Args>
void ascending_print(T&& t, Args&&... args)
{
ascending_print(std::forward<T>(t));
ascending_print(std::forward<Args>(args)...);
}
上面的函数打印提供的所有参数。下面是我们如何编写descending_print()
:
template<typename T, int... Is>
void call_ascending_print(T&& t, index_list<Is...>)
{
ascending_print(std::get<Is>(std::forward<T>(t))...);
}
template<typename... Args>
void descending_print(Args&&... args) {
call_ascending_print(make_revert(std::forward<Args>(args)...),
index_range<0, sizeof...(Args)>());
}
又是一个简单的测试用例:
int main()
{
ascending_print(42, 3.14, "Hello, World!");
std::cout << std::endl;
descending_print(42, 3.14, "Hello, World!");
}
当然还有一个活生生的例子
最后一步:简化
上面的解决方案可能不容易理解,但可以使使用变得很简单,并且非常灵活。给定两个泛型函数:
template<typename F, typename... Args, int... Is>
void revert_call(F&& f, index_list<Is...>, Args&&... args)
{
auto rt = make_revert(std::forward<Args>(args)...);
f(std::get<Is>(rt)...);
}
template<typename F, typename... Args>
void revert_call(F&& f, Args&&... args)
{
revert_call(f, index_range<0, sizeof...(Args)>(),
std::forward<Args>(args)...);
}
和一些宏定义(对不起,我找不到为函数模板创建重载集的方法):
#define MAKE_REVERT_CALLABLE(func)
struct revert_caller_ ## func
{
template<typename... Args> void operator () (Args&&... args)
{ func(std::forward<Args>(args)...); }
};
#define REVERT_ADAPTER(func)
revert_caller_ ## func()
让任何函数都能很容易地以相反的顺序调用参数
MAKE_REVERT_CALLABLE(ascending_print)
template<typename... Args>
void descending_print(Args&&... args)
{
revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...);
}
int main()
{
ascending_print(42, 3.14, "Hello, World!");
std::cout << std::endl;
descending_print(42, 3.14, "Hello, World!");
}
最后,像往常一样,一个活生生的例子
我认为,而不是颠倒参数,你可以颠倒你的逻辑!例如,反转对实参的操作。
template <typename T>
void ascendingPrint(const T& x)
{
cout << x << " ";
}
template<typename T, typename ... Args>
void ascendingPrint(const T& t, Args... args)
{
ascendingPrint(t); // First print `t`
ascendingPrint(args...); // Then print others `args...`
}
template <typename T>
void descendingPrint(const T& x)
{
cout << x << " ";
}
template<typename T, typename ... Args>
void descendingPrint(const T& t, Args... args)
{
descendingPrint(args...); // First print others `args...`
descendingPrint(t); // Then print `t`
}
和
int main()
{
ascendingPrint(1, 2, 3, 4);
cout << endl;
descendingPrint(1, 2, 3, 4);
}
输出1 2 3 4
4 3 2 1
这是我在注释中提到的简单方法:反向生成索引并使用它解压缩元组。
// reversed indices...
template<unsigned... Is> struct seq{ using type = seq; };
template<unsigned I, unsigned... Is>
struct rgen_seq : rgen_seq<I-1, Is..., I-1>{};
template<unsigned... Is>
struct rgen_seq<0, Is...> : seq<Is...>{};
#include <tuple>
namespace aux{
template<class Tup, unsigned... Is>
void descending_print(Tup&& t, seq<Is...>)
{
ascending_print(std::get<Is>(std::forward<Tup>(t))...);
}
} // aux::
template<class... Args>
void descending_print(Args&&... args)
{
auto t = std::forward_as_tuple(std::forward<Args>(args)...);
aux::descending_print(t, rgen_seq<sizeof...(Args)>{});
}
生活例子。
这是一个专门化revert<>
的递归实现:
// forward decl
template<class ...Tn>
struct revert;
// recursion anchor
template<>
struct revert<>
{
template<class ...Un>
static void apply(Un const&... un)
{
ascendingPrint(un...);
}
};
// recursion
template<class T, class ...Tn>
struct revert<T, Tn...>
{
template<class ...Un>
static void apply(T const& t, Tn const&... tn, Un const&... un)
{
// bubble 1st parameter backwards
revert<Tn...>::apply(tn..., t, un...);
}
};
// using recursive function
template<class A, class ...An>
void descendingPrint(A const& a, An const&... an)
{
revert<An...>::apply(an..., a);
}
它与gcc-4.6/7/8和clang一起工作,并且可能符合标准—唯一困难的部分是revert<Tn...>::apply(tn..., t, un...)
的调用。
虽然它有缺点(递归经常有),它生成大量的目标函数的模板实例化(代码膨胀),并且不使用完美转发,这可能是一个问题(但可能可以改进使用它)。
这可以使用c++ 17折表达式和一个从右向左顺序执行的小技巧来完成。
#include <iostream>
template< typename T> void print(T&& val) { std::cout << val; }
template< typename ... Types > void descendingPrint(Types&&... vals) {
int tmps = 0;
((print(vals), tmps) = ...);
}
int main() {
descendingPrint(1, ' ', 2, ' ', 3);
return 0;
}
我的解决方案支持完美转发,不涉及递归:
#include <iostream>
#include <utility>
#include <tuple>
#include <cstdlib>
template< typename ...types >
void
ascendingPrint(types &&... _values)
{
(std::cout << ... << std::forward< types >(_values)) << std::endl;
}
template< typename ...types, std::size_t ...indices >
void
descendingPrintHelper(std::tuple< types... > const & refs, std::index_sequence< indices... >)
{
constexpr std::size_t back_index = sizeof...(indices) - 1;
return ascendingPrint(std::forward< std::tuple_element_t< back_index - indices, std::tuple< types... > > >(std::get< back_index - indices >(refs))...);
}
template< typename ...types >
void
descendingPrint(types &&... _values)
{
auto const refs = std::forward_as_tuple(std::forward< types >(_values)...);
return descendingPrintHelper(refs, std::make_index_sequence< sizeof...(types) >{});
}
int
main()
{
ascendingPrint(1, ' ', 2, ' ', 3);
descendingPrint(1, ' ', 2, ' ', 3);
return EXIT_SUCCESS;
}
实例(或更简单)。
现代编译器也可以完美地优化掉所有不必要的东西:https://godbolt.org/g/01Qf6w
- 将可变参数函数的参数封装在类实例中
- QML 使用带有参数C++函数
- 使用可变参数函数作为模板参数
- 如何在C++中伪造虚拟可变参数函数模板?
- 为什么可变参数函数不适用于模板
- C++ std::functional 中的可变参数函数模板
- 可变参数函数指针的定义对于VxWorks spyLib来说不清楚
- 使用可变参数函数覆盖具有不同函数签名的虚函数
- 考虑引用和常量的可变参数函数包装器
- 使用可变参数函数将整数和/或整数数组放入单个 int 数组中
- 在可变参数函数中转发特定范围的参数
- 通过引用传递参数;函数返回类型是否必须为 VOID?
- 使用带有一个参数函数的递归找到数字的平方
- 可变参数函数模板不能很好地使用 std::function 作为参数
- 多个可变参数函数的单个模板参数包?
- 参数数据类型未知的可变参数函数
- 可变参数函数参数包扩展
- 使用模板可变参数函数将多个参数传递给另一个函数
- 对可变参数函数的递归调用的链接器错误
- 通过像printf这样的可变参数函数传递一个带有常量字符*转换函数的类