11

Während ich mit compile-time Zeichenfolge (variadic Listen von char) Manipulation, musste ich implementieren eine Möglichkeit zu überprüfen, ob eine Kompilierzeit Zeichenfolge enthielt eine andere (kleiner) Kompilierzeit-String.Generische Art und Weise zu bewerten (short-circuiting) Template bedingten Typen

Dies war mein erster Versuch:

template<int I1, int I2, typename, typename> struct Contains; 

template<int I1, int I2, char... Cs1, char... Cs2> 
struct Contains<I1, I2, CharList<Cs1...>, CharList<Cs2...>> 
{ 
    using L1 = CharList<Cs1...>; 
    using L2 = CharList<Cs2...>; 
    static constexpr int sz1{L1::size}; 
    static constexpr int sz2{L2::size}; 

    using Type = std::conditional 
    < 
     (I1 >= sz1), 
     std::false_type, 
     std::conditional 
     < 
      (L1::template at<I1>() != L2::template at<I2>()), 
      typename Contains<I1 + 1, 0, L1, L2>::Type, 
      std::conditional 
      < 
       (I2 == sz2 - 1), 
       std::true_type, 
       typename Contains<I1 + 1, I2 + 1, L1, L2>::Type 
      > 
     > 
    >; 
}; 

ich diese Lösung extrem einfach zu lesen und Grund finden. Leider funktioniert nicht.

Der Compiler versucht immer, jeden einzelnen Zweig von std::conditional zu instanziieren, auch solche, die nicht belegt sind. Um es anders auszudrücken, Kurzschluss ist nicht passiert.

Dadurch wird Contains unendlich instanziiert.

Ich habe mein ursprüngliches Problem gelöst, indem ich jeden std::conditional Block in einer separaten Vorlagenklasse getrennt habe, wo die Bedingungsergebnisse als Teilspezialisierungen behandelt werden.

Es funktioniert, aber leider finde ich es sehr schwer zu lesen/zu ändern.


Gibt es eine Möglichkeit, eine Vorlage Typ lazily zu instanziiert und zu meiner ursprünglichen Lösung der Nähe? Diese

ist ein Beispiel dafür, was der Code aussehen könnte:

using Type = std::conditional 
< 
    (I1 >= sz1), 
    std::false_type, 
    std::conditional 
    < 
     (L1::template at<I1>() != L2::template at<I2>()), 
     DeferInstantiation<typename Contains<I1 + 1, 0, L1, L2>::Type>, 
     std::conditional 
     < 
      (I2 == sz2 - 1), 
      std::true_type, 
      DeferInstantiation<typename Contains<I1 + 1, I2 + 1, L1, L2>::Type> 
     > 
    > 
>; 

Ist es irgendwie möglich DeferInstantiation<T> zu implementieren?

Antwort

4

Hier ist eine generische Vorlage durch die verzögerte Instanziierung zu erlauben, einfach nicht :)

template <bool B, template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ArgsTuple> 
struct LazyConditional; 

template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args> 
struct LazyConditional<true, TrueTemplate, FalseTemplate, std::tuple<Args...>> 
{ 
    using type = TrueTemplate<Args...>; 
}; 

template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args> 
struct LazyConditional<false, TrueTemplate, FalseTemplate, std::tuple<Args...>> 
{ 
    using type = FalseTemplate<Args...>; 
}; 

Für Vollständigkeit, ein einfaches Beispiel demonstriert die Verwendung der Instanziierung:

#include <iostream> 
#include <type_traits> 
#include <tuple> 

template <typename T> 
struct OneParam 
{ 
    void foo(){std::cout << "OneParam" << std::endl;} 
}; 

template <typename T, typename U> 
struct TwoParam 
{ 
    void foo(){std::cout << "TwoParam" << std::endl;} 
}; 

template <bool B, template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ArgsTuple> 
struct LazyConditional; 

template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args> 
struct LazyConditional<true, TrueTemplate, FalseTemplate, std::tuple<Args...>> 
{ 
    using type = TrueTemplate<Args...>; 
}; 

template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args> 
struct LazyConditional<false, TrueTemplate, FalseTemplate, std::tuple<Args...>> 
{ 
    using type = FalseTemplate<Args...>; 
}; 

template <typename ... Args> 
struct OneOrTwoParam 
{ 
    using type = typename LazyConditional<sizeof...(Args)==1, OneParam, TwoParam, std::tuple<Args...> >::type; 
}; 

int main() 
{ 
    OneOrTwoParam<int>::type().foo(); 
    OneOrTwoParam<int, int>::type().foo(); 
    return 0; 
} 

Diese Drucke:

OneParam 
TwoParam 
2

Der Compiler versucht immer zu instanti aß jeden einzelnen Zweig von std :: conditional, auch diejenigen, die nicht genommen wurden. Um es anders auszudrücken, ist ein Kurzschluss nicht passiert.

std::conditional<B,T,F> ist für den Zweck vorgesehen, um eine Auswahl zwischen compiletime gegebenen TypenT und F auszuführen, abhängig von den Booleschen B. Die Wahl erfolgt durch Spezialisierung.Wenn B wahr ist, die instanziiert Spezialisierung:

std::conditional<true,T,F> 
{ 
    typedef T type; 
}; 

Und wenn B falsch ist, die die instanziiert Spezialisierung:

std::conditional<false,T,F> 
{ 
    typedef F type; 
}; 

Beachten Sie, dass entweder Spezialisierung, sowohl T und F muss instanziiert instanziiert werden. Es gibt keine "Verzweigungen". Der Begriff des "Kurzschließens" der Instanziierung von entweder std::conditional<true,T,F> oder std::conditional<false,T,F> könnte nur bedeuten nicht tun.

Also nein, ist es nicht möglich DeferInstantiation<U> zu implementieren, für Typ-Parameter U, so dass eine Instantiierung

std::conditional<{true|false},DeferInstantiation<T>,DeferInstantiation<F>> 

nicht zur Folge hat Instanziierung DeferInstantiation<T> und DeferInstantiation<F>>, und deshalb von T, und von F.

Für eine compiletime Wahl der Ausführung als an dem oder zwei oder mehr Vorlagen instanziiert werden soll, stellt die Sprache Spezialisierung (wie gerade von std::conditional<B,T,F> durch die Definition dargestellt selbst); es bietet Funktion Vorlage Überlast Auflösung, und es bietet SFINAE. Spezialisierung und Überladungsauflösung kann jeder synergetisch mit SFINAE genutzt werden, über die Bibliothek Unterstützung von std::enable_if<B,T>

Das Problem, das Sie in der Gestaltung der besonderen rekursiven Meta-Funktion , die Sie wollen behindert hat, ist nicht eine zwischen bestimmten der Wahl Typen, aber der Wahl der Vorlage , in die rekursive Instanziierung gerichtet werden soll. std::conditional ist nicht zum Zweck. @ Pradhans Antwort zeigt, dass eine Vorlage, die sich von std::conditional unterscheidet, gut geschrieben werden kann, um eine Compiletime-Auswahl zwischen zwei Vorlagen ohne zu bewirken, was zur Folge hat, dass beide instanziiert werden. Er wendet Spezialisierung an, um es zu tun.

Wie Sie bereits gesagt haben, haben Sie bereits eine Spezialisierungslösung für das Problem gefunden. Dies ist im Prinzip der richtige Weg, um die Template-Auswahl in rekursiven Meta-Funktionen rekursiv zu steuern. Mit dem Aufkommen von constexpr, rekursive Meta-Funktionen nicht mehr als der Marktanteil von Probleme, die sie früher getan haben, und die meisten der Gehirnschmerzen, die sie verursacht ist eine Sache der Vergangenheit.

Das besondere Problem hier - bei compiletime zu bestimmen, ob eine Zeichenfolge ein Teil eines andere ist - ohne die Auseinandersetzung mit Vorlage Meta-Programmierung gelöst werden, und ohne compiletime Saiten anders als traditionellen Stringliterale darstellt:

#include <cstddef> 

constexpr std::size_t str_len(char const *s) 
{ 
    return *s ? 1 + str_len(s + 1) : 0; 
} 

constexpr bool 
is_substr(char const * src, char const *targ, 
      std::size_t si = 0, std::size_t ti = 0) 
{ 
    return !targ[ti] ? true : 
       str_len(src + si) < str_len(targ + ti) ? false : 
        src[si] == targ[ti] ? 
         is_substr(src,targ,si + 1, ti + 1) : 
          is_substr(src,targ,si + 1, 0); 
} 

// Compiletime tests... 

static_assert(is_substr("",""),""); 
static_assert(is_substr("qwerty",""),""); 
static_assert(is_substr("qwerty","qwerty"),""); 
static_assert(is_substr("qwerty","qwert"),""); 
static_assert(is_substr("qwerty","werty"),""); 
static_assert(is_substr("qwerty","wert"),""); 
static_assert(is_substr("qwerty","er"),""); 
static_assert(!is_substr("qwerty","qy"),""); 
static_assert(!is_substr("qwerty","et"),""); 
static_assert(!is_substr("qwerty","qwertyz"),""); 
static_assert(!is_substr("qwerty","pqwerty"),""); 
static_assert(!is_substr("","qwerty"),""); 

int main() 
{ 
    return 0; 
} 

Dies wird als C++ 11 oder besser kompilieren.

Sie haben auch Gründe für den Wunsch compiletime Strings als CharList<char ...> andere als wodurch sie zugänglich TMP compiletime Anfragen wie diese darzustellen. Wir können sehen, dass CharList<char ...Cs> ein statisches Konstante size Mitglied muss sizeof...(Cs) Bewertung und hat eine statische at<N>() Memberfunktion zum N ten der ...Cs auswertet. In diesem Fall (unter der Annahme, dass at<N>() gedebuggt wird), könnte man anpassen is_substr eine Template-Funktion zu erwarten CharList<char ...> Parameter auf etwa den folgenden Zeilen:

#include <type_traits> 

template< 
    class SrcList, class TargList, std::size_t SrcI = 0, std::size_t TargI = 0> 
constexpr typename 
std::enable_if<(TargI == TargList::size && SrcI <= SrcList::size),bool>::type 
is_substr() 
{ 
    return true; 
} 

template< 
    class SrcList, class TargList, std::size_t SrcI = 0, std::size_t TargI = 0> 
constexpr typename 
std::enable_if<(TargI < TargList::size && SrcI == SrcList::size),bool>::type 
is_substr() 
{ 
    return false; 
} 

template< 
    class SrcList, class TargList, std::size_t SrcI = 0, std::size_t TargI = 0> 
constexpr typename 
std::enable_if<(TargI < TargList::size && SrcI < SrcList::size),bool>::type 
is_substr() 
{ 
    return SrcList::template at<SrcI>() == TargList::template at<TargI>() ? 
       is_substr<SrcList,TargList,SrcI + 1,TargI + 1>() : 
       is_substr<SrcList,TargList,SrcI + 1,0>(); 
} 

, die die Anwendung von SFINAE zeigt, durch std::enable_if Leveraged

Schließlich könnte man auch in diesem Programm interessiert sein:

#include <iostream> 

template<char const * Arr> 
struct string_lit_type 
{ 
    static constexpr const char * str = Arr; 
    static constexpr std::size_t size = str_len(str); 
    static constexpr char at(std::size_t i) { 
     return str[i]; 
    } 
}; 

constexpr char arr[] = "Hello World\n"; 

int main() 
{ 
    std::cout << string_lit_type<arr>::str; 
    std::cout << string_lit_type<arr>::size << std::endl; 
    std::cout << string_lit_type<arr>::at(0) << std::endl; 
    return 0; 
} 

, die Drucke:

Hello World 
12 
H 

(Code mit g ++ kompiliert 4,9, klirren 3.5)