2016-06-03 18 views
7

Ich möchte eine Funktionsvorlage schreiben, die auf einem Container mit Zeichenfolgen ausgeführt wird, z. B. std::vector.Implementieren einer Kompilierzeit "static-if" -Logik für verschiedene Zeichenfolgetypen in einem Container

Ich möchte sowohl CString als auch std::wstring mit der gleichen Template-Funktion unterstützen.

Das Problem ist, dass CString und wstring verschiedene Schnittstellen haben, zum Beispiel die „Länge“ eines CString zu erhalten, rufen Sie die GetLength() Methode, anstatt für wstring Sie size() oder length() nennen.

Wenn wir hatten ein „statisch, wenn“ Funktion in C++, könnte ich schreiben so etwas wie:

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

Gibt es eine Template-Programmierung Technik, um dieses Ziel mit den derzeit verfügbaren C++ zu erreichen 11/14 Werkzeuge?

PS
Ich weiß, es ist möglich, ein paar DoSomething() Überlastungen mit vector<CString> und vector<wstring> zu schreiben, aber das ist nicht der Punkt, der Frage.
Darüber hinaus möchte ich, dass diese Funktionsvorlage für jeden Container funktioniert, auf dem Sie mit einer Range-for-Schleife iterieren können.

+0

https://www.youtube.com/watch?v=hDwhfjBPKv8 gute Blitz Diskussion über das Thema aus dem Treffen C++ 2015 – odinthenerd

Antwort

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

Kein Weg !! Nett!!!! – Smeeheey

+0

Da ich nur eine Antwort markieren kann, wähle ich diese, weil sie der Struktur eines "statischen if" ähnelt. Wenn es möglich ist, bevorzuge ich einfachere Lösungen wie Traits oder Funktionsüberlastungen. Vielen Dank für die Freigabe Ihres Codes. –

+0

Es scheint, als ob das Lambda noch kompilieren und an die entsprechende static_if-Funktion übergeben werden muss. Wenn dies kompiliert wird, liegt es wahrscheinlich daran, dass der Compiler den nicht verwendeten Parameter in den beiden oberen Definitionen entfernt. Es sollte aber immer noch schlecht geformt sein, es sei denn, ich vermisse etwas ... – patatahooligan

3

Sie zwei Überlastungen für das Erhalten der Länge zur Verfügung stellen könnte:

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

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

Sie Funktion Überlastungen bieten könnte, die tun, was Sie brauchen:

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

Ein üblicher Weg, dies zu lösen, ist die erforderliche zu extrahieren Schnittstelle in eine Merkmalklasse. Etwas wie folgt aus:

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

Natürlich können Sie dann Lust gehen und die Funktion ändern sich Merkmalsselektion zusätzlich zu der Art basierte Auswahl zu ermöglichen:

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

Ich glaube, 'StringTraits ' wird nicht korrekt innerhalb des Bereichs für Schleife, unter Berücksichtigung der 'const auto & s' iteration ('für (const auto & s: strings) ...'). Die Verwendung des "value_type" des Containers ist stattdessen eine bessere Alternative. –

+0

@ Mr.C64 'value_type' muss nicht existieren. Ich habe den 'declltype' Gebrauch behoben. – Angew

4

Hier ist eine mit einer schönen Syntax.

Das Ziel ist es, die zusätzliche () s in @ Piotr-Lösung loszuwerden.

Viele vorformulierten:

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

Hier ist, wie es am Verwendungsort aussieht:

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

mit einer unbegrenzten Kette von static_else_if unterstützt s.

Es verhindert nicht, dass Sie eine unbegrenzte Kette von static_else (static_else im obigen ist nur ein Alias ​​für static_else_if<true>) tun.