2017-07-11 56 views
6

Spędziłem kilka ostatnich dni próbując stworzyć uogólnione opakowanie sortowania dla wskaźników funkcji w C++ i udało mi się rozwiązać prawie każdy pasek pojedynczego problemu. Moim głównym celem było po prostu wywołanie obiektu jako funkcji ze wskaźnikiem funkcji przechowywanym wewnętrznie. Jeśli wskazówka wskazywałaby gdzieś, to nazwałaby to normalnie, podczas gdy wskaźnik zerowy nie wywoływałby tej funkcji i działałby tak, jakby nic się nie stało. Zamierzam użyć tego przede wszystkim do celów funkcji zwrotnej, gdzie prawdopodobnie nie dbałbym o to, czy funkcja została wywołana czy nie i po prostu chciałem wykonać akcję. To działa prawie idealnie z następujących czynności:Użycie zestawu parametrów C '...' jako wartości szablonu C++?

template<typename T> 
class Action; 

template<typename TReturn, typename ... TArgs> 
class Action<TReturn(TArgs...)> { 
public: 
    //! Define a type for the Action object 
    typedef TReturn(*signature)(TArgs...); 

    //! Constructors 
    inline Action(const signature& pFunc = nullptr) : mPointer(pFunc) {} 
    inline Action(const Action& pCopy) : mPointer(pCopy.mPointer) {} 

    //! Operator Call 
    inline bool operator() (TReturn& pReturn, TArgs ... pArgs) const { if (!mPointer) return false; pReturn = mPointer(pArgs...); return true; } 

    //! Operators 
    inline Action& operator=(const Action& pCopy) { mPointer = pCopy.mPointer; return *this; } 
    inline Action& operator=(const signature& pFunc) { mPointer = pFunc; return *this; } 
    inline operator bool() const { return (mPointer != nullptr); } 

private: 
    //! Store a pointer to the callback function 
    signature mPointer; 
}; 

template<typename ... TArgs> 
class Action<void(TArgs...)> { 
public: 
    //! Define a type for the Action object 
    typedef void(*signature)(TArgs...); 

    //! Constructors 
    inline Action(const signature& pFunc = nullptr) : mPointer(pFunc) {} 
    inline Action(const Action& pCopy) : mPointer(pCopy.mPointer) {} 

    //! Operator Call 
    inline bool operator() (TArgs ... pArgs) const { if (!mPointer) return false; mPointer(pArgs...); return true; } 

    //! Operators 
    inline Action& operator=(const Action& pCopy) { mPointer = pCopy.mPointer; return *this; } 
    inline Action& operator=(const signature& pFunc) { mPointer = pFunc; return *this; } 
    inline operator bool() const { return (mPointer != nullptr); } 

private: 
    //! Store a pointer to the callback function 
    signature mPointer; 
}; 

Jednak czuję się z sytuacją, że najprawdopodobniej to wykorzystanie wrapper jest wyjście z informacji debugowania lub sformatowanego tekstu. Może to wynikać z funkcji zdefiniowanych przez użytkownika lub wbudowanych funkcji, takich jak printf. Aby dopasować podpis printf dodatkowych działań byłyby tworzone jak:

Action<int(const char*, ...)> callback = printf; 

i byłoby w stanie działać w ten sam sposób, że wszelkie inne działania będą zachowywać. Problem, który znajduję, polega na tym, że "..." wymusi podpis szablonu, aby nie dopasować się do żadnej z tych specjalizacji, zamiast tego iść z pierwszym, który jest tylko prototypem.

W pełni rozumiem, dlaczego to nie działa i dlaczego kompilator nie byłby w stanie obsłużyć generowania wymaganej klasy, ale miałem nadzieję, że ktoś tutaj zna jakieś podstępne sposoby, aby to osiągnąć lub coś podobnego. Każda pomoc będzie mile widziane, thanks :)

+7

Dlaczego nie skorzystać z 'std :: function'? – StoryTeller

+0

Jaki jest cel "działania"? Jakie masz wymagania, że ​​nie możesz użyć ['std :: function'] (http://en.cppreference.com/w/cpp/utility/functional/function)? Jaki problem rozwiązuje "Action", że 'std :: function' does not? –

+1

https://strojverflow.com/questions/18370396/why-cant-stdfunction-bind-to-c-style-variadic-functions dał kilka komentarzy, jak osiągnąć prawie to. Myślę, że dla każdej funkcji variadic potrzebujesz konkretnej klasy, która Ci pomoże, i mam nadzieję, że istnieje wariant v * funkcji, którą możesz wywołać. – mksteve

Odpowiedz

1

Poniższy przykład działa dla wszystkich typów funkcji, a lambdas bez wychwytywania:

#include <utility> 
#include <cstdio> 
#include <cmath> 

template<typename Fn> 
class Action { 
    Fn* function_ptr;   
public: 
    Action() noexcept : function_ptr(nullptr) {}  
    Action(std::nullptr_t) noexcept : function_ptr(nullptr) {}  
    Action(const Action& other) : function_ptr(other.function_ptr) {}  
    Action(Fn f) : function_ptr(f) {} 

    Action& operator=(const Action& other) { 
     return (function_ptr = other.function_ptr, *this); 
    }    
    Action& operator=(std::nullptr_t) { 
     return (function_ptr = nullptr, *this); 
    }  
    Action& operator=(Fn f) { 
     return (function_ptr = f, *this); 
    } 

    template<typename... Params> 
    auto operator()(Params&&... params) { 
     return function_ptr(std::forward<Params>(params)...); 
    } 
}; 

Live Demo

W komentarzach pod pytaniem wzmianki , używanie std::function jest lepsze niż pisanie otoki dla wskaźników funkcji. Jedynym problemem z std::function jest to, że nie można go używać z sygnaturami funkcji, które zawierają elipsę. Jeśli jawnie określisz podpis, który możesz przechowywać, np. printf następująco:

std::function<int(const char*, int, double, double)> fn = printf; 

Jeśli używasz C++ 17 lub wzmocnienia, można zaimplementować jesteś właścicielem printf -jak funkcję, za pomocą std::any lub Boost.Any który może być przypisany do std::function następująco:

#include <iostream> 
#include <string> 
#include <vector> 
#include <any> 
#include <functional> 

using namespace std; 

void any_printf(string&& format, vector<any>&& args) { 
    int arg_index = 0; enum { NORMAL, CONVERT } mode; 

    for(auto& chr : format) { 
     if(mode == CONVERT) { 
      switch(chr) { 
      case 'd': cout << any_cast<int>(args[arg_index++]); break; 
      case 'f': cout << any_cast<float>(args[arg_index++]); break; 
      case 's': cout << any_cast<string>(args[arg_index++]); break; 
      /* ... */ 
      default: cout << chr; 
      }; 
      mode = NORMAL; 
     } 
     else { 
      chr == '%' ? (mode = CONVERT, 0) : (cout << chr, 0); 
     } 
    } 
} 

int main() { 
    using namespace string_literals; 
    function<void(string&&, vector<any>&&)> f_ptr { any_printf }; 

    f_ptr("Cuboid: %smm x %dmm x %fmm.\n", { any("3"s), any(4), any(6.67f) }); 
    return 0; 
} 
+0

To jest ciekawy sposób na sprawdzenie, czy działa.Mój jedyny problem polega na tym, że aby uzyskać podpis do szablonu, musi istnieć wartość. Nie jest łatwo proste opóźnienie żądanych wywołań funkcji bez uwzględnienia co najmniej jednej funkcji pasującej do sytuacji. Domyśliłem się, że dokładny typ składni, po której się znajdowałem, będzie bliski, jeśli nie niemożliwy. Dzięki za szybką odpowiedź :) – MitchellCroft

+1

@MitchellCroft, gdy napiszesz funkcję, która wywoła wywołanie zwrotne, musisz zdefiniować jej podpis. Jeśli chcesz ** wywołać to wywołanie zwrotne ze zmienną liczbą danych i typami zmiennych ** (jak można wywołać 'printf'), możesz użyć np. 'std :: vector' typu [' std :: any'] (http://en.cppreference.com/w/cpp/utility/any). Jest to funkcja C++ 17, ale [Boost.Any] (http://www.boost.org/doc/libs/1_64_0/doc/html/any.html) również może być użyty jako podstawienie. – Akira

+0

@MitchellCroft, Edytowałem swoją odpowiedź za pomocą podejścia 'std :: any', o którym wspomniałem w poprzednim komentarzu. – Akira