Python的一个很好的功能是你可以使用属性来包装一个函数。 例如,您可能需要将函数应用于每个参数和返回值,以将它们转换为函数可以处理的函数或调用它的命令。
如何在编译时使用c ++执行此操作? 换句话说,如何定义一个需要两个的函数模板包装 模板函子作为类型和函子func,然后返回一个以上述方式包装的lambda?
为了使这个想法更具体,我们有以下几点。 这里的想法是你有一个带有Args ...的函数f,并返回ReturnVals ... 你想要一个带有h1(Args)....的函数g并返回h2(ReturnVals)......
换句话说,g = h2(f(h1(Args)......)....)。
类似于Python-like-C-decorators。不同之处在于可以在编译时完全使用宏来完成此操作。这样它应该没有开销和类型安全。您应该能够这样做是有道理的,因为编译器知道所有相关信息。
答案 0 :(得分:0)
我实际上已经想出了如何做到这一点,并认为堆栈溢出的人可能对该方法感兴趣。该代码可在Gitlab repository上公开获取。
可悲的是,完全通用性相当复杂,但下面的代码可行。 首先,包括一些元编程头和一个type_trait来检查是否 type是另一种类型的特化。
#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;
};
};
现在,我定义了允许您应用函数的辅助函数 一个解压缩的元组,(只是一个版本的std :: apply from c ++ 17),和 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))...);
}
}
现在我定义一个函数,它接受它的参数并将它包装在一个元组中,如果它不是一个,但是如果是它只是让它保持不变。这很好,因为你可以用它调用另一个函数(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> {});
}