2015-04-08 17 views
9

vorrei scrivere la funzione come questo find:funzione template con i corrispondenti parametri per sottoinsieme di tipi tuple

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

O

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

Dove find può essere parametrizzata corrispondente a qualsiasi sottoinsieme dei parametri di tuple .

Il mio codice finora:

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 
    } 
} 

risposta

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

E 'anche possibile avere find prendere un parametro di tipo pack e perfettamente in avanti, piuttosto che prendere i tipi fissi con tuple_element. Il vantaggio è che è possibile evitare una conversione non necessaria se == è trasparente. Il costo è che non è possibile prendere qualsiasi cosa che non può essere perfettamente inoltrata (ad es. Elenchi di inizializzazione rinforzati, 0 come costante di puntatore nullo). Un vantaggio collaterale sembra essere che MSVC 2013, non soffocare in questa versione:

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

Ciò fornisce al tuo insieme le prestazioni lineari nelle sue ricerche ... piuttosto debole da una prospettiva di complessità algoritmica. – StilesCrisis

+0

@StilesCrisis i dati che l'OP sta inserendo non sono ordinati? Per inciso, questo diventa molto meno ottuso in C++ 1z. – Yakk

+0

Se il tuo '==' è trasparente, quanto sopra è inefficiente. Ad esempio, 'std :: string' e' "ciao mondo" '. – Yakk

-2

la firma della funzione find sarebbe

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

l'implementazione della ricerca è a voi, è possibile utilizzare i modelli ricorsive o loop sopra parametro pacchetti

+0

Perché stai forzando 'find' a prendere in base al valore? – Yakk

+0

@Yakk ah grazie per la cattura – Valerij

+0

È consigliabile utilizzare l'inoltro perfetto laddove possibile quando si scrivono i modelli – Fiktik

1

Questa è una funzione che prende un valore di seme, e una serie di lambda. Si nutre di quel valore del seme attraverso ciascuno dei lambda a sua volta:

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

Dentro la classe, si usa il sopra:

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

questo compila in clang, ma non g ++ 4.9.2 - g ++ non ama i pacchetti di parametri all'interno di lambda.

Nota il fatto che prendiamo Us const&... - questo consente di trasparente ==, che è importante in alcuni casi. std::string == char const* è un esempio classico, in cui se si forza lo find a prendere lo stesso valore della tupla, si imporrà un'allocazione inutile chiamando lo find.


In C++ 1z, la chiamata chain possono essere sostituiti con:

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

che è concettualmente identico, ma molto più facile da leggere. Questo è noto come "espressione di piega".


Ora, un problema con quanto sopra è che utilizza i riferimenti di inoltro, che causa problemi di inoltro imperfette di inoltro perfetto.

Il più fastidioso di cui è l'impossibilità di utilizzare {} per costruire argomenti.

Se usiamo tipi di corrispondenza, abbiamo invece forzare confronto non trasparente, che può essere costoso (esaminare std::string rispetto al "hello this is a c string" -. Provoca eventualmente assegnazione se si forza la stringa c in una std::string)

A questo modo è a 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'; 
} 

poi riscrivere 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)); 
     }... 
    ); 
    } 
); 
} 

che fa entrambe uguaglianza trasparente e permette {} costruzione basata (bene, ma richiede {{}} costruzione basata - l'esterno dire stiamo costruendo la gomma, l'interno per costruire il T).

+0

@ T.C. perché rimuovere il seme 'true'? Non è necessario per un pacchetto di argomenti vuoto? – Yakk

+0

No, piegando un pacchetto vuoto con && produce 'true'. –

+0

@ T.C. bah, cosa succede se il mio operatore && 'è sovraccaricato su o valori insieme? "Caso speciale il caso più comune" pfah "user friendly". – Yakk