2016-11-04 5 views
-2

Eine der netten Eigenschaften von Python ist, dass Sie Eigenschaften verwenden können, um eine Funktion zu umbrechen. Beispielsweise müssen Sie möglicherweise eine Funktion auf jedes der Argumente anwenden und Werte zurückgeben, um sie in etwas zu konvertieren, mit dem Ihre Funktion umgehen kann oder das der aufrufende Befehl verarbeiten kann.Einpacken einer Funktion mit C++

Wie machen Sie das mit C++ zur Kompilierzeit? Mit anderen Worten, wie definieren Sie einen Funktionsvorlagenumbruch, der zwei Vorlagen-Funktoren als Typen und eine Funktorfunktion verwendet, und gibt dann ein Lambda zurück, das in der oben beschriebenen Weise verpackt ist?

Um zu versuchen, die Idee etwas konkreter zu machen, haben wir folgendes. Die Idee hier ist, Sie haben eine Funktion f, die Args ... nimmt, und ReturnVals ... Sie möchten eine Funktion g, die h1 (Args) .... und gibt h2 (ReturnVals) ...

Mit anderen Worten, g = h2 (f (h1 (Args) ...) ....).

Es ist vergleichbar mit Python-like-C-decorators. Der Unterschied besteht darin, dass dies vollständig zur Kompilierzeit ohne Makros möglich ist. So sollte es kein Overhead sein und typsicher sein. Es macht Sinn, dass Sie das können, weil alle relevanten Informationen dem Compiler bekannt sind.

+0

Sie möchten also eine Python-Funktion aus C++ aufrufen? –

+4

Pseudo-Code (dh. Python) Beispiel wäre nett. – Lunaweaver

+1

@ChrisBritt, nein, das glaube ich nicht. Die Frage ist eher wie "Hier ist ein Feature, das Python hat. Hat C++ diese Funktion auch?" – Kevin

Antwort

0

Ich habe tatsächlich herausgefunden, wie dies zu tun ist, und dachte, dass Leute auf Stapelüberlauf an der Methode interessiert sein könnten. Der Code ist öffentlich unter Gitlab repository verfügbar.

Leider ist es ziemlich kompliziert in voller Allgemeinheit, aber der Code unten funktioniert. Fügen Sie zuerst einige Metaprogrammierungsheader und einen Type_trait hinzu, um zu prüfen, ob ein Typ eine Spezialisierung eines anderen Typs ist.

#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 {};  

Wir definieren dann eine closure_traits Funktion, die uns die Argumenttypen und die Anzahl der Argumente einer Funktion ableiten lässt. Ich bin dankbar für die freundlichen Leute, die meine question in Bezug darauf, wie dies zu tun ist beantwortet.

/* 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;         

    };                            

};   

Jetzt definiere ich auf Hilfsfunktionen, mit denen Sie eine Funktion ein entpackten Tupel, anzuwenden (Nur eine Version von std :: gelten von C++ 17) und foreach_in_tuple, die Ihnen erlaubt, Wenden Sie einen Funktor auf jedes Element im Tupel an. Dies ist rekursiv, weshalb Sie die Hilfsfunktionen benötigen.

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))...);             

}                            

}                             

Nun definiere ich eine Funktion, die ihr Argument nimmt und wickelt es in einem Tupel, wenn es nicht bereits ist, aber wenn es sie ist es nur Blätter die gleiche. Das ist nett, weil Sie dann eine andere Funktion (f) mit ihm umbrechen und dann annehmen können, dass die umbrochene Funktion ein Tupel zurückgibt.

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));                 

}                             

Diese beiden Funktionen sind nur etwas weniger generische Version von C++ 17 std :: anwenden und eine Funktion, die einen Funktor auf jedes Element in einem Tupel gilt.

Die folgende Funktion wendet die Funktion auf jedes Argument an, indem sie es mit rekursiven Methoden entpackt. Es ist die gleiche Methode, die in den Methoden apply_imp und foreach_in_tupl_impl oben verwendet wird. Es werden auch die Rückgabewerte umbrochen.

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;                      

    }                            


}                             

Die Funktion, die tatsächlich die Verpackung ausführt.

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