2016-02-09 8 views
5

Ich brauche eine Template-Funktion:Compose aufrufbare Objekt

template <typename ...Signatures, typename ...Functions> 
auto make_callable_object (Functions&& ...functions); 

, die ein aufrufbare Objekt zurück. Wenn dieses aufrufbare Objekt mit einer Signatur aus Signaturen aufgerufen wird, sollten die entsprechenden Funktionen von Funktionen aufgerufen werden.

Z. B .:

auto co = create_callable_object<void (int), void (std::string), void (std::exception)> (
      [] (int i) { std::cout << "int"; }, 
      [] (std::string) { std::cout << "string"; } 
      [] (std::exception) { std::cout << "exception"; } 
     ); 

sollte ein Objekt zurückgeben, die

struct { 
    void operator() (int) const { std::cout << "int"; } 
    void operator() (std::string) const { std::cout << "string"; } 
    void operator() (std::exception) const { std::cout << "exception"; } 
}; 

entspricht ich eine Implementierung erstellt, aber es lässt sich nicht kompilieren mit Klirren C++ 11.

Ich bin mir nicht sicher, ob es einen Compiler Bug oder einen Fehler in meinem Code gibt. Ich suche nach Workarounds oder vielleicht eine bessere Lösung, die mit gcc und clang im C++ 11-Modus kompilieren würde.

Coliru link

#include <iostream> 
#include <tuple> 
#include <type_traits> 
#include <functional> 

#define noexcept 
#define constexpr 
#define constexpr14 

struct callable_impl_base { 
    // will trigger if called with bad signature 
    template <typename ...Ts> 
    void operator() (Ts...) const { throw std::bad_function_call {}; } 
}; 

template <typename Func, typename Base, typename Sig> 
struct callable_impl; 

template <typename Func, typename Base, typename R, typename ...Args> 
struct callable_impl<Func, Base, R (Args...)>: public Base 
{ 
    template <typename FF> 
    constexpr callable_impl (FF&& f, Base&& b) 
    : Base (std::forward<Base> (b)) 
    , func (std::forward<FF> (f)) 
    { 
    } 

    // unhiding method from the base classes. 
    using Base::operator(); 

    constexpr R operator() (Args&& ...args) const 
    { 
    return func (std::forward<Args> (args)...); 
    } 

    constexpr14 R operator() (Args&& ...args) 
    { 
    return func (std::forward<Args> (args)...); 
    } 

    Func func; 
}; 

template <typename Sig, typename Func, typename Base> 
constexpr 
callable_impl< 
    typename std::decay<Func>::type 
    , typename std::decay<Base>::type 
    , Sig 
> 
make_callable_impl (Func&& func, Base&& base) 
{ 
    return { std::forward<Func> (func), std::forward<Base> (base) }; 
} 

// Recursion stopper. 
template <typename ...> 
constexpr callable_impl_base 
make_callable() { return {}; } 

// Strip first Sig and first Func one by one. 
template <typename Sig, typename ...Sigs, typename F, typename ...Fs> 
constexpr14 auto 
make_callable (F&& f, Fs&& ...fs) 
    -> decltype (make_callable_impl<Sig> (
     std::forward<F> (f), 
     make_callable<Sigs...> (std::forward<Fs> (fs)...))) 
{ 
    static_assert (sizeof... (Sigs) == sizeof... (Fs), "bad number of args"); 

    return make_callable_impl<Sig> (
     std::forward<F> (f), 
     make_callable<Sigs...> (std::forward<Fs> (fs)...)); 
} 

using namespace std; 

struct A {}; 
struct B {}; 

int main() 
{ 
    auto x = make_callable<void (const A&), void(B const&), void(int,int,int)> (
       [] (A const&) {cout << "A\n";}, 
       [] (B const&) {cout << "B\n";}, 
       [] (int,int,int) { cout << "int,int,int\n"; } 
); 

    x (B{}); 
    x (A{}); 
    x (1,2,4); 

    // this must throw because of incompatible signature. 
    try { 
    x (1,2); 
    } 
    catch (std::bad_function_call) 
    { 
     std::cout << "x (1,2) -> got exception (ok)\n"; 
    } 
} 

UPDATE:

habe ich versucht, eine andere Lösung und erhalten expliziter Signaturen in make_callable Template-Parameter befreien. So, jetzt die Compose-Funktion ist in einer solchen einfachen Weg genannt:

int main() 
{ 
    using namespace std; 

    auto co = make_callable (
     [] (int) { cout << "int\n"; }, 
     [] (string) { cout << "string\n"; }, 
     [] (int,int) { cout << "int,int\n"; } 
); 

    cout << "co(\"str\") -> "; co ("fff"); 
    cout << "co(55) -> "; co (55); 
    cout << "co(55, 44) -> "; co (55, 44); 

    // This must throw exception. 
    try { co ('c', 4, 4); } 
    catch (bad_function_call) { cout << "co('c',4,4) -> exception (ok)\n"; } 

} 

Und hier ist Coliru Demo. Aber ich bin mir immer noch nicht sicher, ob es sehr effektiv ist oder eine viel bessere Lösung möglich ist.

+1

Für das, was es wert ist, Ihr Code erscheint mit [gcc] (http zu arbeiten: // rextester.com/REA61905) und [VC++] (http://rextester.com/QYSBXB97285), aber nicht klingeln. –

+0

Aus Neugier: Warum sollten Sie jemals die nicht-strikte Version verwenden? Ich würde immer bevorzugen, dass der Compiler mir sagt, dass die Signatur falsch ist. – Rumburak

Antwort

2

Die folgende Vorgehensweise ist kürzer (und meiner Meinung nach leichter grok):

Sie die Arbeit Grunzen hat zum Erfassen, ob eine aufrufbare eine Übereinstimmung ist:

struct can_call_test 
{ 
    template<typename F, typename... A> 
    static decltype(std::declval<F>()(std::declval<A>()...), std::true_type()) 
    f(int); 

    template<typename F, typename... A> 
    static std::false_type 
    f(...); 
}; 

} // namespace detail 

template<typename F, typename... A> 
struct is_callable : decltype(detail::can_call_test::f<F, A...>(0)) { }; 

template<typename F, typename... A> 
struct is_callable <F(A...)> : is_callable <F, A...> { }; 

Lets Ausweichcallables definieren, damit wir behandeln die nicht gefunden Fall identisch mit allen anderen:

template <bool Strict> 
struct Fallback 
{ 
    template<typename... Args, typename T = int> 
    void operator()(Args&&...) const 
    { 
    static_assert (sizeof(T) == 0, 
     "Bad function call: incompatible signature, see next error message"); 
    } 
}; 

template <> 
struct Fallback<false> 
{ 
    template<typename... Args> 
    void operator()(Args&&...) const 
    { 
    throw std::bad_function_call {}; 
    } 
}; 

, nun ein Tupel von Callables gegeben, lässt herauszufinden der Index des ersten Anpassungs aufrufbar:

template <size_t Idx, typename Tuple, typename... Args> 
struct FirstCallable; 

template <size_t Idx, typename C, typename... Rest, typename... Args> 
struct FirstCallable<Idx, std::tuple<C, Rest...>, Args...> 
{ 
    static constexpr size_t index = 
     is_callable<C, Args...>::value 
      ? Idx 
      : FirstCallable<Idx + 1, std::tuple<Rest...>, Args...>::index; 
}; 

template <size_t Idx, typename C, typename... Args> 
struct FirstCallable<Idx, std::tuple<C>, Args...> 
{ 
    static constexpr size_t index = Idx; 
}; 

Die callable Vorlage kann vollständig Ausweichunabhängig sein, was recht praktisch sein könnte, tatsächlich, wenn eines Tages Sie ein anderes Verhalten haben möchten (z eine andere Ausnahme). Es muss nur das erste passende Tupel-Element aufgerufen werden:

template <class... Functions> 
struct callable 
{ 
    using FTuple = std::tuple<Functions...>; 

    FTuple functions; 

    template <typename Tuple> 
    callable(Tuple&& f) 
     : functions(std::forward<Tuple>(f)) 
    { 
    } 

    template <class... Args> 
    auto operator()(Args&&... args) const 
     -> decltype(std::get<FirstCallable<0, FTuple, Args...>::index>(functions)(
      std::forward<Args>(args)...)) 
    { 
    return std::get<FirstCallable<0, FTuple, Args...>::index>(functions)(
     std::forward<Args>(args)...); 
    } 
}; 

Die Factory-Funktionen fügen schließlich den Fallback hinzu.

template <class... Functions> 
callable<typename std::decay<Functions>::type..., Fallback<true>> 
make_strict_callable(Functions&&... functions) 
{ 
    return {std::forward_as_tuple(std::forward<Functions>(functions)..., 
           Fallback<true>{})}; 
} 

template <class... Functions> 
callable<typename std::decay<Functions>::type..., Fallback<false>> 
make_callable(Functions&&... functions) 
{ 
    return {std::forward_as_tuple(std::forward<Functions>(functions)..., 
           Fallback<false>{})}; 
} 

-Code auch hier gepostet: http://coliru.stacked-crooked.com/a/3942dfd3de7c1ef8

+0

Vielen Dank für die coole Lösung. Ich mag die Art, wie du Fallback eingefügt hast. Die (direkte) Art, wie Sie die Indizes berechnen, ist auch cool. Ich brauche "make_callable" -Funktion, um einen intelligenten Besucher zu erstellen, um boost :: variant-Typen in Parsern und Interpretern zu inspizieren oder zu modifizieren. Ich muss das Problem dort zur Laufzeit behandeln, also benutze ich nicht-strikte Callables. Ich habe eine Demo erstellt, wie es verwendet werden kann. Aber interessant, die Demo mit Ihrer make_callable-Implementierung konnte nicht mit clang kompiliert werden (gcc ist in Ordnung). Kannst du es dir anschauen? http://coliru.stacked-crooked.com/a/9dae246d14c9ae78 –

+1

Ich ersetzte die c-style-Ellipse durch variadische Vorlagen (und ich korrigierte die Bedingung in der 'static_assert', um tatsächlich zu feuern). Ich habe den neuen Fallback mit Ihrem Variantencode ausprobiert: http://coliru.stacked-crooked.com/a/baf62b65f4219bb5 – Rumburak

+0

Und hier ist eine nette Antwort, warum der Ellipsenweg nur mit einigen Compilern funktioniert: http: // stackoverflow. com/a/23773551/2173029 – Rumburak