2016-06-03 31 views
7

Vorrei scrivere un modello di funzione che funzioni su un contenitore di stringhe, ad esempio uno std::vector.Implementazione di una logica "static-if" in fase di compilazione per diversi tipi di stringhe in un contenitore

Mi piacerebbe supportare sia CString e std::wstring con la stessa funzione di modello.

Il problema è che CString e wstring hanno interfacce diverse, ad esempio per ottenere la "lunghezza" di un CString, si chiama il metodo GetLength(), invece, per wstring si chiama size() o length().

Se avessimo un "statico se" funzione in C++, potrei scrivere qualcosa di simile:

template <typename ContainerOfStrings> 
void DoSomething(const ContainerOfStrings& strings) 
{ 
    for (const auto & s : strings) 
    { 
     static_if(strings::value_type is CString) 
     { 
      // Use the CString interface 
     } 
     static_else_if(strings::value_type is wstring) 
     { 
      // Use the wstring interface 
     } 
    } 
} 

C'è qualche tecnica di programmazione modello di raggiungere questo obiettivo con attualmente disponibili C++ 11/14 utensili?

PS
So che è possibile scrivere un paio di DoSomething() sovraccarichi con vector<CString> e vector<wstring>, ma non è questo il punto della questione.
Inoltre, mi piacerebbe che questo modello di funzione funzionasse per qualsiasi contenitore su cui è possibile iterare utilizzando un ciclo range-for.

+0

https://www.youtube.com/watch?v=hDwhfjBPKv8 buon discorso lampo sull'argomento dall'incontro C++ 2015 – odinthenerd

risposta

16
#include <type_traits> 

template <typename T, typename F> 
auto static_if(std::true_type, T t, F f) { return t; } 

template <typename T, typename F> 
auto static_if(std::false_type, T t, F f) { return f; } 

template <bool B, typename T, typename F> 
auto static_if(T t, F f) { return static_if(std::integral_constant<bool, B>{}, t, f); } 

template <bool B, typename T> 
auto static_if(T t) { return static_if(std::integral_constant<bool, B>{}, t, [](auto&&...){}); } 

Test:

template <typename ContainerOfStrings> 
void DoSomething(const ContainerOfStrings& strings) 
{ 
    for (const auto & s : strings) 
    { 
     static_if<std::is_same<typename ContainerOfStrings::value_type, CString>{}> 
     ([&](auto& ss) 
     { 
      // Use the CString interface 
      ss.GetLength(); 
     })(s); 

     static_if<std::is_same<typename ContainerOfStrings::value_type, wstring>{}> 
     ([&](auto& ss) 
     { 
      // Use the wstring interface 
      ss.size(); 
     })(s); 
    } 
} 

DEMO

+2

Assolutamente no !! Bello!!!! – Smeeheey

+0

Dato che posso contrassegnare solo una risposta, la scelgo perché assomiglia alla struttura di un "statico se". Tuttavia, quando possibile, preferisco soluzioni più semplici come i tratti o sovraccarichi di funzione. Grazie per aver condiviso il tuo codice. –

+0

Sembra che il lambda debba ancora essere compilato e passato alla funzione static_if appropriata, quindi se questo compila è probabilmente perché il compilatore ottimizza il parametro inutilizzato nelle prime due definizioni. Dovrebbe comunque essere mal formata, a meno che non manchi qualcosa ... – patatahooligan

3

Si potrebbe fornire due overload per ottenere la lunghezza:

template<typename T> 
std::size_t getLength(T const &str) 
{ 
    return str.size(); 
} 

std::size_t getLength(CString const &str) 
{ 
    return str.GetLength(); 
} 
5

Si potrebbe fornire sovraccarichi di funzioni che fare ciò che devi:

size_t getSize(const std::string& str) 
{ 
    return str.size(); 
} 

size_t getSize(const CString& str) 
{ 
    return str.GetLength(); 
} 

template <typename ContainerOfStrings> 
void DoSomething(const ContainerOfStrings& strings) 
{ 
    for (const auto & s : strings) 
    { 
     ... 
     auto size = getSize(s); 
     ... 
    } 
} 
4

Un modo comune per risolvere questo è quello di estrarre il necessario interfaccia in una classe di tratto. Qualcosa di simile a questo:

template <class S> 
struct StringTraits 
{ 
    static size_t size(const S &s) { return s.size(); } 
    // More functions here 
}; 


template <typename ContainerOfStrings> 
void DoSomething(const ContainerOfStrings& strings) 
{ 
    for (const auto & s : strings) 
    { 
     auto len = StringTraits<typename std::decay<decltype(s)>::type>::size(s); 
    } 
} 


// Anyone can add their own specialisation of the traits, such as: 

template <> 
struct StringTraits<CString> 
{ 
    static size_t size(const CString &s) { return s.GetLength(); } 
    // More functions here 
}; 

Naturalmente, si può quindi andare fantasia e cambiare la funzione stessa per consentire la selezione tratto in aggiunta al tipo a base di selezione:

template <class ContainerOfStrings, class Traits = StringTraits<typename ContainerOfString::value_type>> 
void DoSomething(const ContainerOfStrings& strings) 
+0

Credo che 'StringTraits ' non funzionerà correttamente all'interno del range per loop, considerando il 'const auto & s' iteration ('for (const auto & s: stringhe) ...'). L'uso del 'value_type' del contenitore è invece un'alternativa migliore. –

+0

@ Mr.C64 'value_type' non è necessario. Ho corretto l'uso di 'decltype'. – Angew

4

Ecco uno con una bella sintassi.

L'obiettivo è quello di eliminare gli extra () s nella soluzione di @ Piotr.

sacco di testo standard:

template<bool b> 
struct static_if_t {}; 
template<bool b> 
struct static_else_if_t {}; 

struct static_unsolved_t {}; 

template<class Op> 
struct static_solved_t { 
    Op value; 
    template<class...Ts> 
    constexpr 
    decltype(auto) operator()(Ts&&...ts) { 
    return value(std::forward<Ts>(ts)...); 
    } 
    template<class Rhs> 
    constexpr 
    static_solved_t operator->*(Rhs&&)&&{ 
    return std::move(*this); 
    } 
}; 
template<class F> 
constexpr 
static_solved_t<std::decay_t<F>> static_solved(F&& f) { 
    return {std::forward<F>(f)}; 
} 

template<class F> 
constexpr 
auto operator->*(static_if_t<true>, F&& f) { 
    return static_solved(std::forward<F>(f)); 
} 
template<class F> 
constexpr 
static_unsolved_t operator->*(static_if_t<false>, F&&) { 
    return {}; 
} 
constexpr 
static_if_t<true> operator->*(static_unsolved_t, static_else_if_t<true>) { 
    return {}; 
} 
constexpr 
static_unsolved_t operator->*(static_unsolved_t, static_else_if_t<false>) { 
    return {}; 
} 

template<bool b> 
constexpr static_if_t<b> static_if{}; 

template<bool b> 
constexpr static_else_if_t<b> static_else_if{}; 

constexpr static_else_if_t<true> static_else{}; 

Ecco come si presenta al punto di utilizzo:

template <typename ContainerOfStrings> 
void DoSomething(const ContainerOfStrings& strings) { 
    for (const auto & s : strings) 
    { 
    auto op = 
    static_if<std::is_same<typename ContainerOfStrings::value_type,CString>{}>->* 
    [&](auto&& s){ 
     // Use the CString interface 
    } 
    ->*static_else_if<std::is_same<typename ContainerOfStrings::value_type, std::cstring>{}>->* 
    [&](auto&& s){ 
     // Use the wstring interface 
    }; 
    op(s); // fails to compile if both of the above tests fail 
    } 
} 

con una catena illimitata di static_else_if s supportati.

Non impedisce di eseguire una catena illimitata di static_else (static_else in quanto sopra è solo un alias per static_else_if<true>).