用 C++ 包装一个函数
Wrapping a function with c++
Python 的一个不错的功能是您可以使用属性来包装函数。
例如,您可能需要将一个函数应用于每个参数和返回值,以将它们转换为您的函数可以处理或调用它的命令可以处理的内容。
你如何在编译时使用 C++ 做到这一点?
换句话说,你如何定义一个函数模板包装,它需要两个
模板仿函数作为类型和仿函数 func,然后 returns 以上述方式包装的 lambda?
为了使这个想法更具体一点,我们有以下内容。
这里的想法是你有一个函数 f 接受 Args...,和 returns ReturnVals...
你想要一个接受 h1(Args).... 和 returns h2(ReturnVals)...
的函数 g
换句话说,g = h2(f(h1(Args)...)...)。
类似于。不同之处在于是否可以在没有宏的情况下完全在编译时执行此操作。这样它应该没有开销并且类型安全。您应该能够这样做是有道理的,因为编译器知道所有相关信息。
我实际上想出了如何做到这一点,并认为堆栈溢出的人可能对该方法感兴趣。该代码可在 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 函数,它允许我们推导出
函数的参数类型和参数数量。
我感谢那些回答我 关于如何做到这一点的好心人。
/* 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))...);
}
}
现在我定义了一个函数,它接受它的参数并将它包装在一个元组中,如果它还不是一个元组,但如果是它就保持不变。这很好,因为您可以用它调用包装另一个函数 (f),然后假设包装函数 return 是一个元组。
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 方法中使用的方法相同。
它还包含 return 值。
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> {});
}
Python 的一个不错的功能是您可以使用属性来包装函数。 例如,您可能需要将一个函数应用于每个参数和返回值,以将它们转换为您的函数可以处理或调用它的命令可以处理的内容。
你如何在编译时使用 C++ 做到这一点? 换句话说,你如何定义一个函数模板包装,它需要两个 模板仿函数作为类型和仿函数 func,然后 returns 以上述方式包装的 lambda?
为了使这个想法更具体一点,我们有以下内容。 这里的想法是你有一个函数 f 接受 Args...,和 returns ReturnVals... 你想要一个接受 h1(Args).... 和 returns h2(ReturnVals)...
的函数 g换句话说,g = h2(f(h1(Args)...)...)。
类似于
我实际上想出了如何做到这一点,并认为堆栈溢出的人可能对该方法感兴趣。该代码可在 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 函数,它允许我们推导出
函数的参数类型和参数数量。
我感谢那些回答我
/* 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))...);
}
}
现在我定义了一个函数,它接受它的参数并将它包装在一个元组中,如果它还不是一个元组,但如果是它就保持不变。这很好,因为您可以用它调用包装另一个函数 (f),然后假设包装函数 return 是一个元组。
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 方法中使用的方法相同。 它还包含 return 值。
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> {});
}