我实际上想出了如何做到这一点,并认为堆栈溢出的人可能对该方法感兴趣。该代码在Gitlab repository 上公开提供。
遗憾的是,总体而言,它相当复杂,但下面的代码可以工作。
首先,包括一些元编程头文件和一个 type_trait 来检查是否
一种类型是另一种类型的特化。
#include <type_traits>
#include <tuple>
#include <utility>
#include <functional>
#include "arma_wrapper.h"
/* Tests if U is a specialization of T */
template<template<typename...> typename T, typename U>
struct is_specialization_of : std::false_type {};
template<template <typename ...> typename T, typename... Args>
struct is_specialization_of<T, T<Args...>> : std::true_type {};
然后我们定义一个closure_traits 函数,它允许我们推断
函数的参数类型和参数数量。
我很感谢那些回答我的question 关于如何做到这一点的好心人。
/* For generic types use the type signature of their operator() */
template <typename T>
struct closure_traits :
public closure_traits<decltype(&T::operator())> {};
/*
* This is adapted from the stack overflow question
* Is it possible to figure out the parameter type and return type
* of a lambda.
*/
template <typename ClassType, typename ReturnType,
typename... ArgTypes>
struct closure_traits<ReturnType (ClassType::*) (ArgTypes... args)
const>
{
using arity = std::integral_constant<std::size_t,
sizeof...(ArgTypes)>;
using Ret = ReturnType;
template <std::size_t I>
struct Args {
using type = typename std::tuple_element<I,
std::tuple<ArgTypes...>>::type;
};
};
template <typename ClassType, typename ReturnType,
typename... ArgTypes>
struct closure_traits<ReturnType (ClassType::*) (ArgTypes... args)>
{
using arity = std::integral_constant<std::size_t,
sizeof...(ArgTypes)>;
using Ret = ReturnType;
template <std::size_t I>
struct Args {
using type = typename std::tuple_element<I,
std::tuple<ArgTypes...>>::type;
};
};
现在,我定义了帮助函数,允许您将函数应用于
一个未打包的元组,(只是 c++17 中 std::apply 的一个版本),以及
foreach_in_tuple 允许您将仿函数应用于
一个元组。这是以递归方式进行的,这就是您需要辅助函数的原因。
namespace detail {
/*
* This function defines a generic lambda that takes can be applied
* to every one of the arguments in the tuple. It requires that
* Func has a overload for operator() that can be applied to each of
* the parameters. Then it applies this lambda to each of the
* parameters in the tuple.
*/
template<typename Func, typename TupleType, std::size_t... I>
decltype(auto) for_each_impl(TupleType&& tup,
std::index_sequence<I...>) {
auto func_impl = [] (auto&& x) {
Func func;
return func(std::forward<decltype(x)>(x));
};
return std::make_tuple(func_impl(std::get<I>
(std::forward<TupleType>(tup)))...);
}
/* My version of c++17 apply_impl method. */
template<typename FuncType, typename TupleType, std::size_t... I>
decltype(auto) apply_impl(FuncType&& func, TupleType&& tup,
std::index_sequence<I...>) {
return func(std::get<I>(std::forward<TupleType>(tup))...);
}
}
现在我定义一个函数,它接受它的参数并将它包装在一个元组中,如果它还不是一个,但如果它是它就让它保持不变。这很好,因为你可以用它调用 wrap 另一个函数 (f),然后假设被包装的函数返回一个元组。
template<typename T>
auto idempotent_make_tuple(T&& arg) ->
std::enable_if_t<is_specialization_of<std::tuple,T>::value, T&&> {
return arg;
}
template<typename T>
auto idempotent_make_tuple(T&& arg) ->
std::enable_if_t<! is_specialization_of<std::tuple, T>::value,
decltype(std::make_tuple(std::forward<T>(arg)))> {
return std::make_tuple(std::forward<T>(arg));
}
这两个函数只是 c++17 的一个稍微不太通用的版本
std::apply 和一个将仿函数应用于元组中每个元素的函数。
template<typename FuncType, typename TupleType>
decltype(auto) apply(FuncType&& func, TupleType&& tup) {
return detail::apply_impl(std::forward<FuncType>(func),
std::forward<TupleType>(tup),
std::make_index_sequence<std::tuple_size<
std::decay_t<TupleType>>::value>{});
}
/* Applies a Functor Func to each element of the tuple. As you might
* expect, the Functor needs overloads for all of the types that in
* the tuple or the code will not compile.
*/
template<typename Func, typename TupleType>
decltype(auto) for_each_in_tuple(TupleType&& tup) {
return detail::for_each_impl<Func>(std::forward<TupleType>(tup),
std::make_index_sequence<std::tuple_size<
std::decay_t<TupleType>>::value>{});
}
以下函数通过使用递归方法将函数解包,将函数应用于每个参数。它与上述 apply_imp 和 foreach_in_tupl_impl 方法中使用的方法相同。
它还包装了返回值。
namespace detail {
/*
* This function takes a function and an index sequence with its
* number of arguments. It then figures out the types of its
* arguments, and creates a new function with each of the
* arguments and each of the returned values converted to the
* new types.
*/
template<typename ArgWrapper, typename ReturnWrapper,
typename FuncType, size_t...I>
auto wrap_impl(FuncType&& func, std::index_sequence<I...>) {
/*
* This is used to figure out what the argument types of
* func are
*/
using traits = closure_traits<
typename std::decay_t<FuncType>>;
auto wrapped_func = [=] (std::result_of_t<ReturnWrapper(
typename traits:: template Args<I>::type)>... args) {
/*
* Apply the argument wrapper function to each of the
* arguments of the new function.
*/
decltype(auto) tup1 = for_each_in_tuple<ArgWrapper>
(std::forward_as_tuple(args...));
/* Apply the old function to the wrapped arguments. */
decltype(auto) tup2 = idempotent_make_tuple(apply(func,
std::forward<std::decay_t<decltype(tup1)>>(tup1)));
/*
* Apply the Return wrapper to the return value of the
* old function
*/
decltype(auto) tup3 = for_each_in_tuple<ReturnWrapper>(
std::forward<std::decay_t<decltype(tup2)>>(tup2));
return tup3;
};
return wrapped_func;
}
}
实际执行包装的函数。
template<typename ArgWrapper, typename ReturnWrapper,
typename FuncType>
auto wrap(FuncType&& func) {
return detail::wrap_impl<ArgWrapper, ReturnWrapper>(
std::forward<FuncType>(func),
std::make_index_sequence<closure_traits<
FuncType>::arity::value> {});
}