2015-04-08 6 views
9

von Tupel-Typen der Teilmenge Ich möchte Funktion schreiben, da dies find:Template-Funktion Parameter mit entsprechenden

multi_set<int, string, double, myType> m; //vector of tuples 
m.insert(/*some data*/); 
m.find<1,2>("something",2.123); 

Oder

m.find<0,3>(1,instanceOfMyType); 
m.find<1>("somethingelse"); 

Wo find parametrisiert werden kann, um jede Untergruppe von Tupel Parametern entsprechen .

Mein Code so weit:

template <typename ... T> 
class multi_set{ 
    typedef tuple <T...> Tuple; 
    vector<tuple<T...>> data = vector<tuple<T...>>(); 

public: 
    void insert(T... t){ 
     data.push_back(tuple<T...>(t...)); 
    } 


    template<size_t ... Pos> 
    void find(???){ 
    // then I would like to use those params to search through data and 
    // return first matching item 
    } 
} 

Antwort

5
// test whether a particular tuple is a match 
template<size_t... Pos> 
static bool is_match(const Tuple& tuple, const typename std::tuple_element<Pos, Tuple>::type &... args) { 
    std::initializer_list<bool> results = { (std::get<Pos>(tuple) == args)... }; 
    return std::all_of(results.begin(), results.end(), [](bool p) { return p; }); 
} 

// Find the first one that is a match. 
template<size_t... Pos> 
typename vector<Tuple>::const_iterator find(const typename std::tuple_element<Pos, Tuple>::type &... args) const { 
    return std::find_if(data.begin(), data.end(), [&](const Tuple & tup) { return is_match<Pos...>(tup, args...); }); 
} 

Es ist auch möglich, find nehmen einen Typparameter Pack zu haben und perfekt nach vorne, anstatt mit tuple_element festen Typen nehmen. Der Vorteil ist, dass Sie eine unnötige Konvertierung vermeiden können, wenn == transparent ist. Die Kosten sind, dass Sie nichts nehmen können, das nicht mehr perfekt weitergeleitet werden kann (z. B. abgestützte Initialisierungslisten, 0 als Nullzeigerkonstante). Ein positiver Nebeneffekt scheint zu sein, dass MSVC 2013 nicht auf dieser Version nicht ersticken:

// test whether a particular tuple is a match 
template<size_t... Pos, class... Args> 
static bool is_match(const Tuple& tuple, Args&&... args) { 
    std::initializer_list<bool> results = { (std::get<Pos>(tuple) == std::forward<Args>(args))... }; 
    return std::all_of(results.begin(), results.end(), [](bool p) { return p; }); 
} 

// Find the first one that is a match. 
template<size_t... Pos, class... Args> 
typename vector<Tuple>::const_iterator find(Args&&... args) const { 
    return std::find_if(data.begin(), data.end(), [&](const Tuple & tup) { return is_match<Pos...>(tup, std::forward<Args>(args)...); }); 
} 
+0

Dies gibt Ihrem Satz eine lineare Leistung bei seinen Suchen ... ziemlich schwach aus einer algorithmischen Komplexitätsperspektive. – StilesCrisis

+0

@StilesCrisis sind die Daten, die das OP einfügt, nicht geordnet? Nebenbei, das wird in C++ 1z viel weniger stumpf. – Yakk

+0

Wenn Ihr '==' transparent ist, ist das obige ineffizient. Als Beispiel, "std :: string" und "Hallo Welt". – Yakk

-2

die Signatur Ihrer find Funktion

template<size_t ... Pos, typename ... Types> void find(Types... &t) 

die Umsetzung der Suche ist bis zu Ihnen sein würde, Sie rekursive Vorlagen oder Schleifen über Parameter verwenden können Packs

+0

Warum erzwingen Sie 'finden', um Wert zu nehmen? – Yakk

+0

@Yakk ah danke für den Haken – Valerij

+0

Sie sollten beim Schreiben von Vorlagen die perfekte Weiterleitung verwenden, wo immer dies möglich ist – Fiktik

1

Dies ist eine Funktion, die einen Startwert, und eine Reihe von lambdas nimmt. Er ernährt sich, dass das Saatgut Wert durch jede der lambdas wiederum:

template<class... Fs, class R> 
R chain(R r, Fs&&... fs) { 
    using in_order = int[]; 
    (void)(in_order{0, 
    (
     (r = std::forward<Fs>(fs)(r)) 
     , void(), 0 
    )... 
    }); 
    return r; 
} 

Innerhalb Ihrer Klasse, verwenden wir die oben:

template<size_t... Pos, class...Us> 
typename std::vector<Tuple>::const_iterator 
find(Us const&... us) const { 
    return std::find_if(
    data.begin(), data.end(), 
    [&](const Tuple & tup) { 
     return chain(
     true, 
     [&](bool old){ 
      return old && (std::get<Pos>(tup) == us); 
     }... 
    ); 
    } 
); 
} 

diese kompiliert in Klirren, aber nicht g ++ 4.9.2 - g ++ mag keine Parameterpakete in Lambdas.

Beachten Sie die Tatsache, die wir nehmen Us const&... - dies ermöglicht transparent ==, die in einigen Fällen wichtig ist. std::string == char const* ist ein klassisches Beispiel, wo, wenn Sie erzwingen find, um den gleichen Wert wie im Tupel zu nehmen, erzwingen Sie eine unnötige Zuweisung beim Aufruf find.

(... && (std::get<Pos>(tup) == us)) 

die konzeptionell identisch ist, aber viel einfacher zu lesen:


In C++ 1z kann der chain Aufruf ersetzt werden. Dies ist als "Faltenausdruck" bekannt.

Nun, ein Problem mit dem oben genannten ist, dass es Forwarding-Referenzen verwendet, die unvollständige Weiterleitung Probleme der perfekten Weiterleitung verursacht.

Am ärgerlichsten ist die Unfähigkeit, {} zum Konstruieren von Argumenten zu verwenden.

Wenn wir passende Typen verwenden, sondern wir zwingen nicht-transparenten Vergleich, die teuer werden kann (untersuchen std::string im Vergleich zu "hello this is a c string" -. Es verursacht möglicherweise Zuordnung, wenn wir den C-String in eine std::string zwingen)

A Weg um dies ist zu type erase down to the concept of equality with a given type.

template<class...>struct voider{using type=void;}; 
template<class...Ts>using void_t=typename voider<Ts...>::type; 
template<class T>struct tag{using type=T;}; 

template<class...>struct types{using type=types;}; 

template<class T> 
using block_deduction = typename tag<T>::type; 

template<class F, class Sig, class T=void> 
struct erase_view_op; 

template<class F, class R, class...Ts, class T> 
struct erase_view_op<F, R(Ts...), T> 
{ 
    using fptr = R(*)(void const*, Ts&&...); 

    fptr f; 
    void const* ptr; 

private: 
    template<class U> 
    erase_view_op(U&& u, int): 
    f([](void const* p, Ts&&...ts)->R{ 
     U& u = reinterpret_cast<U&>(*static_cast<std::decay_t<U>*>(const_cast<void*>(p))); 
     return F{}(u, std::forward<Ts>(ts)...); 
    }), 
    ptr(static_cast<void const*>(std::addressof(u))) 
    {} 
public: 
    template<class U, class=std::enable_if_t< !std::is_same<std::decay_t<U>,erase_view_op>{} && std::is_convertible< std::result_of_t<F(U,Ts...)>, R >{} >> 
    erase_view_op(U&& u):erase_view_op(std::forward<U>(u), 0){} 

    template<class U=T, class=std::enable_if_t< !std::is_same<U, void>{} >> 
    erase_view_op(block_deduction<U>&& u):erase_view_op(std::move(u), 0){} 

    erase_view_op(erase_view_op const&) = default; 
    erase_view_op(erase_view_op&&) = default; 

    R operator()(Ts... ts) const { 
    return f(ptr, std::forward<Ts>(ts)...); 
    } 
}; 

struct equality { 
    template<class lhs, class rhs> 
    bool operator()(lhs const& l, rhs const& r)const { 
    return l==r; 
    } 
}; 
template<class T> 
using erase_equal_to = erase_view_op< equality, bool(T const&), T >; 
using string_equal_to = erase_equal_to<std::string>; 

int main() { 
    static_assert(std::is_same< bool, std::result_of_t< std::equal_to<>(decltype("hello"), std::string const&) > >{}, "hmm"); 
    string_equal_to s = "hello"; 
    string_equal_to s2 = {{"hello"}}; 
    (void)s2; 
    std::string x = "hello"; 
    std::string y = "jello"; 
    std::cout << s(x) << s(y) << '\n'; 
} 

dann schreiben wir find:

template<size_t... Pos> 
typename std::vector<Tuple>::const_iterator 
find(erase_equal_to< std::remove_reference_t<std::tuple_element_t<Pos, Tuple>> >... us) const { 
    return std::find_if(
    data.begin(), data.end(), 
    [&](const Tuple & tup) { 
     return chain(
     true, 
     [&](bool old){ 
      return old && us(std::get<Pos>(tup)); 
     }... 
    ); 
    } 
); 
} 

die transparente Gleichheit tut beides und ermöglicht {} basierte Konstruktion (na ja, es erfordert {{}} basierte Konstruktion - der äußere den Radierer zu sagen, wir bauen, das Innere, um das T zu konstruieren).

+0

@ T.C. Warum entfernen Sie den Samen "wahr"? Wird es für ein leeres Argument-Pack nicht benötigt? – Yakk

+0

Nein, das Falten einer leeren Packung mit && ergibt 'true'. –

+0

@ T.C. bah, was passiert, wenn mein 'operator &&' überladen ist auf oder Werte zusammen? "Sonderfall der häufigste Fall" "benutzerfreundlich" pfah. – Yakk

Verwandte Themen