用 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> {}); 
}