2013-02-20 10 views
8

Ich möchte ein std::function ähnliches Objekt erstellen, das mit der Speicherung von mehr als einer Überlast umgehen kann."Manuelle" Unterschrift Überladungsauflösung

Syntax sort wie folgt: my_function< int(double, int), double(double, double), char(int, int) >.

Oder deutlicher:

template<typename... Ts> 
struct type_list {}; 

template<typename... Signatures > 
struct my_function { 
    std::tuple< std::function<Signatures>... > m_functions; 
    typedef type_list<Signatures...> sig_list; 
    template<typename... Args> 
    typename pick_overload_signature< sig_list, type_list<Args...> >::return_value 
    operator()(Args&&... args) 
    { 
    return get<pick_overload_signature< sig_list, type_list<Args...> >::index>(m_functions)(std::forward<Args>(args)...); 
    } 
}; 

Meine Frage: wie soll ich schreiben pick_overload_signatures?

Hier ist die Arbeit, die ich auf sie gemacht habe:

Meine Neigung eine partielle Ordnung auf Funktionssignaturen in Bezug auf einen bestimmten Satz von Argumenten zu schreiben wäre, dann die Art Liste von Funktionssignaturen sortieren, dann greifen Sie am besten (mit möglicherweise einer Kompilierungszeit behaupten, dass die beste einzigartig ist). Um das abzuziehen, müsste ich eine solide Teilaufgabe (in Bezug auf eine Reihe übergebener Argumente) auf Funktionssignaturen haben ...

13.3.3.1 sagt mir, wie man feststellen kann, ob es eine gültige Konvertierung gibt . Ich kann dafür schummeln, indem ich den Compiler verwende, um eine Konvertierung für mich durchzuführen, und SFINAE verwenden, um zu erkennen, ob es für ein gegebenes übergebenes Argument und die Signatur einer der "Überladungen" auftrat.

13.3.3.2 sagt mir, wie Sie diese Konvertierungen bestellen. Hier muss ich erkennen, ob eine Konvertierungssequenz benutzerdefiniert oder eine Standardsequenz ist. Ich bin nicht sicher, wie man zwischen den zwei unterscheidet.

Vielleicht kann ich Traces-Klasse verwenden, um das Vorhandensein von benutzerdefinierten Konvertierungssequenzen zu erkennen. Überprüfen Sie auf die Existenz &S::operator D() und &D::D(S const&) und &D::D(S) und &D::D(S&&) oder etwas ähnliches.

has_user_defined_conversion<S,D>::value, has_standard_conversion<S,D>::value, usw.?

Wird dieser Ansatz funktionieren, hat jemand es bereits getan, oder hat jemand bereits Teile davon getan?

Result of Answers

#include <type_traits> 
#include <cstddef> 
#include <utility> 
#include <functional> 
#include <tuple> 
#include <string> 

// Packaged list of types: 
template<typename... Ts> 
struct type_list { 
    template<template<typename...>class target> 
    struct apply { 
     typedef target<Ts...> type; 
    }; 
    template<typename T> 
    struct append { 
     typedef type_list< Ts..., T > type; 
    }; 
    template<typename T> 
    struct prepend { 
     typedef type_list< T, Ts... > type; 
    }; 
}; 
template<template<typename>class mapper, typename list> 
struct map_types { 
    typedef type_list<> type; 
}; 
template<template<typename>class mapper, typename T0, typename... Ts> 
struct map_types<mapper, type_list<T0, Ts...>> { 
    typedef typename map_types<mapper, type_list<Ts...>>::type tail; 
    typedef typename tail::template prepend< typename mapper<T0>::type >::type type; 
}; 
template<template<typename>class mapper, typename list> 
using MapTypes = typename map_types<mapper, list>::type; 
template<template<typename>class temp> 
struct apply_template_to { 
    template<typename T> 
    struct action { 
     typedef temp<T> type; 
    }; 
}; 
template<template<typename> class temp, typename list> 
struct apply_to_each:map_types< apply_template_to<temp>::template action, list > {}; 
template<template<typename> class temp, typename list> 
using ApplyToEach = typename apply_to_each<temp, list>::type; 

template<std::size_t n, typename list> 
struct nth_type {}; 
template<std::size_t n, typename first, typename... elements> 
struct nth_type<n, type_list<first, elements...>>:nth_type<n-1, type_list<elements...>> 
{}; 
template<typename first, typename... elements> 
struct nth_type<0, type_list<first, elements...>> 
{ 
    typedef first type; 
}; 
template<std::size_t n, typename list> 
using NthType = typename nth_type<n, list>::type; 

// func data 
template<typename R, typename... Args> 
struct unpacked_func { 
    typedef R result_type; 
    typedef type_list<Args...> args_type; 
    typedef unpacked_func< R, Args... > unpacked_type; 
    template<template<typename>class target> 
    struct apply { 
     typedef target<R(Args...)> type; 
    }; 
}; 

namespace unpack_details { 
    // Extracting basic function properties: 
    template<typename Func> 
    struct unpack_func {}; 
    template<typename R, typename... Args> 
    struct unpack_func< R(Args...) > { 
     typedef unpacked_func< R, Args... > type; 
    }; 
    template<typename R, typename... Args> 
    struct unpack_func< unpacked_func<R, Args...> >: 
     unpack_func< R(Args...) > 
    {}; 
} 

template<typename Func> 
using FuncUnpack = typename unpack_details::unpack_func<Func>::type; 

template<typename Func> 
struct func_props:func_props<FuncUnpack<Func>> {}; 
template<typename R, typename... Args> 
struct func_props<unpacked_func<R, Args...>>: 
    unpacked_func<R, Args...> 
{}; 

template<typename Func> 
using FuncResult = typename func_props<Func>::result_type; 
template<typename Func> 
using FuncArgs = typename func_props<Func>::args_type; 

template<typename Func> 
struct make_func_ptr:make_func_ptr<FuncUnpack<Func>> {}; 

template<typename R, typename... Args> 
struct make_func_ptr< unpacked_func< R, Args... > > { 
    typedef R(*type)(Args...); 
}; 
template<typename Func> 
using MakeFuncPtr = typename make_func_ptr<Func>::type; 

// Marking a type up with an index: 
template<typename R, std::size_t i> 
struct indexed_type { 
    typedef R type; 
    enum { value = i }; 
}; 

// Sequences of size_t: 
template<std::size_t... s> 
struct seq {}; 
template<std::size_t min, std::size_t max, std::size_t... s> 
struct make_seq: make_seq< min, max-1, max-1, s...> {}; 
template<std::size_t min, std::size_t... s> 
struct make_seq< min, min, s...> { 
    typedef seq<s...> type; 
}; 
template<std::size_t max, std::size_t min=0> 
using MakeSeq = typename make_seq<max, min>::type; 

namespace overload_details { 
    template<std::size_t n, typename... Overloads> 
    struct indexed_linear_signatures {}; 

    template<typename Overload> 
    struct signature_generator {}; 
    template<typename R, typename... Args> 
    struct signature_generator<unpacked_func<R, Args...>> { 
     R operator()(Args...); // no impl 
    }; 


    template<typename Func, std::size_t i> 
    struct indexed_retval {}; 

    template<typename R, typename... Args, std::size_t i> 
    struct indexed_retval< unpacked_func<R, Args...>, i > { 
     typedef unpacked_func<indexed_type<R,i>, Args...> type; 
    }; 

    template<typename Func, std::size_t i> 
    using IndexRetval = typename indexed_retval<Func,i>::type; 

    void test1() { 
     typedef overload_details::IndexRetval< FuncUnpack<void()>, 0 > indexed; 
     indexed::apply<std::function>::type test = []()->indexed_type<void,0> {return indexed_type<void,0>();}; 
    } 

    template<std::size_t n, typename Overload, typename... Overloads> 
    struct indexed_linear_signatures<n, Overload, Overloads...>: 
     signature_generator<IndexRetval<FuncUnpack<Overload>,n>>, 
     indexed_linear_signatures<n+1, Overloads...> 
    {}; 

    template<typename T> 
    struct extract_index {}; 
    template<typename T, std::size_t i> 
    struct extract_index<indexed_type<T,i>> { 
     enum {value = i}; 
    }; 

    template<typename T> 
    using Decay = typename std::decay<T>::type; 

    template<typename indexed_overloads, typename... Args> 
    struct get_overload_index { 
     enum{ value = extract_index< Decay<decltype(std::declval<indexed_overloads>()(std::declval<Args>()...))> >::value }; 
    }; 

    template<typename Overloads, typename Args> 
    struct get_overload {}; 
    template<typename... Overloads, typename... Args> 
    struct get_overload<type_list<Overloads...>, type_list<Args...>> { 
     typedef indexed_linear_signatures<0, Overloads...> sig_index; 
     enum { index = get_overload_index< sig_index, Args... >::value }; 
     typedef FuncUnpack< NthType<index, type_list<Overloads...> > > unpacked_sig; 
    }; 

    template<typename Overloads, typename Args> 
    using GetOverloadSig = typename get_overload< Overloads, Args >::unpacked_sig; 
} 

template<typename Overloads, typename Arguments> 
struct pick_overload_signature { 
    enum{ index = overload_details::get_overload<Overloads, Arguments>::index }; 
    typedef overload_details::GetOverloadSig<Overloads, Arguments> unpacked_sig; 
}; 
#include <iostream> 
void test1() { 
    typedef type_list< void(int), void(double) > overloads; 
    typedef type_list<int> args; 
    typedef pick_overload_signature< overloads, args > result; 
    std::cout << result::index << " should be 0\n"; 
    typedef type_list<double> args2; 
    typedef pick_overload_signature< overloads, args2 > result2; 
    std::cout << result2::index << " should be 1\n"; 

// ; 
    typedef ApplyToEach< std::function, overloads >::apply<std::tuple>::type functions; 
    typedef std::tuple< std::function<void(int)>, std::function<void(double)> > functions0; 
    std::cout << std::is_same<functions, functions0>() << " should be true\n"; 

    functions funcs{ 
     [](int) { std::cout << "int!" << "\n"; }, 
     [](double) { std::cout << "double!" << "\n"; } 
    }; 
    std::get<result::index>(funcs)(0); 
} 

template< typename... Signatures > 
struct my_function { 
    typedef type_list<Signatures...> signatures; 
    typedef std::tuple< std::function<Signatures>... > func_tuple; 
    func_tuple functions; 
    template<typename... Funcs> 
    explicit my_function(Funcs&&... funcs): 
     functions(std::forward<Funcs>(funcs)...) 
    {} 

    template<typename... Args> 
    auto 
    operator()(Args&&... args) const -> 
     typename overload_details::GetOverloadSig< signatures, type_list<Args...> >::result_type 
    { 
     return std::get< 
     pick_overload_signature< signatures, type_list<Args...> >::index 
     >(functions)(std::forward<Args>(args)...); 
    } 
    // copy/assign boilerplate 
    template<typename... OtherSignatures> 
    my_function(my_function<OtherSignatures...> const& o): 
     functions(o.functions) 
    {} 
    template<typename... OtherSignatures> 
    my_function(my_function<OtherSignatures...> && o): 
     functions(std::move(o.functions)) 
    {} 
    template<typename... OtherSignatures> 
    my_function& operator=(my_function<OtherSignatures...> const& o) 
    { 
     functions = o.functions; 
     return *this; 
    } 
    template<typename... OtherSignatures> 
    my_function& operator=(my_function<OtherSignatures...> && o) { 
     functions = std::move(o.functions); 
     return *this; 
    } 
}; 

struct printer { 
    template<typename T> 
    void operator()(T const& t) { 
     std::cout << t << "\n"; 
    } 
}; 

void print(int x) { 
    std::cout << "int is " << x << "\n"; 
} 
void print(std::string s) { 
    std::cout << "string is " << s << "\n"; 
} 
void test2() { 
    my_function< void(int), void(std::string) > funcs{ 
     [](int x){ std::cout << "int is " << x << "\n";}, 
     [](std::string s){ std::cout << "string is " << s << "\n";} 
    }; 
    std::cout << "test2\n"; 
    funcs("hello"); 
    funcs(0); 
    my_function< void(int), void(std::string) > funcs2{ 
     printer(), printer() 
    }; 
    funcs2("hello"); 
    funcs2(12.7); 
    // doesn't work: 
    /* 
    my_function< void(int), void(std::string) > funcs3{ 
     print, 
     print 
    }; 
    */ 
} 
void test3() { 

} 
int main() { 
    test1(); 
    test2(); 
    test3(); 
} 

Ist das nicht getan, sondern ist verwendbar.

Vielen Dank!

+0

Sehr interessanten Frage zufrieden sein wird, und ich habe einige Ideen im Kopf, wie es zu beantworten, aber ich Ich werde damit herumspielen müssen, wenn ich zur Arbeit komme. – Xeo

+0

Möchten Sie in der Lage sein, verschiedene Überladungen der gleichen Funktion oder verschiedene Funktionen, die abhängig von den Argumenten aufgerufen werden sollen, zu speichern?Können Sie ein Beispiel dafür geben, wie Sie Ihre Klassenvorlage verwenden möchten? –

+0

@AndyProwl Ich würde (vorerst) mit verschiedenen Funktionen zufrieden sein, die abhängig von den Argumenten aufgerufen werden, "als ob" sie an der Überladungsauflösung teilnahmen. Sie könnten diese Funktion dann mehrmals als "unterschiedliche Funktionen" wiederholen. Eliminierung dieser Wiederholung könnte gut (und machbar mit ein bisschen Makro + perfekte Weiterleitung Vorlage Lambda Tomfoulery), aber nicht notwendig. – Yakk

Antwort

4

ich bin sicher, dass es Ihr Weg machbar ist, kann aber sein, das Sie mit dieser https://gist.github.com/dabrahams/3779345

template<class...Fs> struct overloaded; 

template<class F1, class...Fs> 
struct overloaded<F1, Fs...> : F1, overloaded<Fs...>::type 
{ 
typedef overloaded type; 

overloaded(F1 head, Fs...tail) 
: F1(head), 
overloaded<Fs...>::type(tail...) 
{} 
using F1::operator(); 
using overloaded<Fs...>::type::operator(); 
}; 

template<class F> 
struct overloaded<F> : F 
{ 
typedef F type; 
using F::operator(); 
}; 

template<class...Fs> 
typename overloaded<Fs...>::type overload(Fs...x) 
{ return overloaded<Fs...>(x...); } 

auto f = overload(
[](int x) { return x+1; }, 
[](char const* y) { return y + 1; }, 
[](int* y) { return y; }); 
+0

Hat das die selbe "Dispatch" Wahl wie das Überladen der Funktion? – Yakk

+1

@Yakk: Es ist notwendig, weil es effektiv eine Überladung für 'operator()' mit den alternativen Signaturen erstellt. –

+0

Ah ja, die 'operator()' sind nicht vertikal verwandt, so dass es keine Präferenz für eine über die andere gibt. Netter Trick - die Anpassung für eine 'std :: function'-Stil-Schnittstelle sollte einfach sein! Der einzige Nachteil ist, dass wir die Mechanismen zur Überladungsauflösung des Compilers direkt verwenden, wir können damit nicht spielen (oder sie ändern). Ich kann auch eine perfekte Weiterleitung durchführen, indem ich das obige in einem nicht bewerteten Kontext verwende, um den Index der richtigen Überladung zu erhalten. – Yakk

1

Ich glaube, Sie so etwas wie diese Eigenschaften verwenden können ... Aber wenn Sie Auflösung machen wollen Überlastung voll wie in Standard - Sie mehr Code benötigen http://en.cppreference.com/w/cpp/language/implicit_cast

#include <type_traits> 

template<typename T, typename D> 
struct is_constructible 
{ 
    template<typename C, typename F> 
    static auto test(C*) -> decltype(C(std::declval<F>()), std::true_type()); 
    template<typename, typename> 
    static std::false_type test(...); 
    static const bool value = std::is_class<T>::value && 
     std::is_same<std::true_type, decltype(test<T, D>(0))>::value; 
}; 

template<typename T, typename D> 
struct has_conversion_operator 
{ 
    static std::true_type test(D d); 
    template<typename C, typename F> 
    static auto test(C* c) -> decltype(test(*c)); 
    template<typename, typename> 
    static std::false_type test(...); 

    static const bool value = std::is_class<T>::value && 
     !is_constructible<T, D>::value && 
     std::is_same<std::true_type, decltype(test<T, D>(0))>::value; 
}; 

template<typename T, typename D> 
struct is_standard_convertible : 
    std::integral_constant<bool, !has_conversion_operator<T, D>::value && 
    !is_constructible<T, D>::value && 
    std::is_convertible<T, D>::value> 
{ 
}; 

template<typename T, typename D> 
struct is_user_convertible : 
    std::integral_constant<bool, has_conversion_operator<T, D>::value || 
    is_constructible<T, D>::value> 
{ 
}; 

und implementieren, was Sie mögen wollen: erste Prüfung , dass Signaturen standard_convertible wenn nicht überprüfen, dass Signatur user_convertible sind.