11

Durante la manipolazione della stringa compilata (elenchi variadici della manipolazione char), avevo bisogno di implementare un modo per verificare se una stringa compilata conteneva un altro (minore) stringa in fase di compilazione.Modo generico per la valutazione pigrizia dei modelli condizionali

Questo è stato il mio primo tentativo:

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

Trovo questa soluzione estremamente facile da leggere e ragionare su. Purtroppo, non funziona.

Il compilatore tenta sempre di creare un'istanza di ogni singolo ramo di std::conditional, anche quelli che non vengono presi. Per dirla in un altro modo, il cor- tocircuito non sta accadendo.

Ciò causa Contains di essere istanziato all'infinito.

Ho risolto il problema originale separando ogni blocco std::conditional in una classe modello separata in cui i risultati della condizione vengono gestiti come specializzazioni parziali.

Funziona, ma sfortunatamente trovo molto difficile leggere/modificare.


Esiste un modo per creare un'istanza di pigramente un tipo di modello ed essere vicino alla mia soluzione originale?

Questo è un esempio di ciò che il codice potrebbe essere simile:

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

E 'in qualche modo possibile implementare DeferInstantiation<T>?

risposta

4

Ecco un modello generico per consentire instantiation differita semplicemente non instantiating :)

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

Per completezza, un semplice esempio che dimostra il suo utilizzo:

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

Questo stampa:

OneParam 
TwoParam 
2

Il compilatore cerca sempre di instanti mangiato ogni singolo ramo di std :: condizionale, anche quelli che non sono presi. Per dirla in un altro modo, il cortocircuito non sta accadendo.

std::conditional<B,T,F> è previsto allo scopo di eseguire una compiletime scelta tra trovati tipiT e F, a seconda della boolean B. La scelta di viene effettuata per specializzazione.Quando B è vero, la specializzazione istanziato è:

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

E quando B è falso, la specializzazione del istanziato è:

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

Si noti che per istanziare sia specializzazione, sia T e F must essere istanziato. Non ci sono "rami". La nozione di "cortocircuito" dell'istanza di std::conditional<true,T,F> o std::conditional<false,T,F> potrebbe significare solo che non lo fa.

Quindi no, non è possibile implementare DeferInstantiation<U>, per il parametro tipo U, tale che un'esemplificazione di

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

non comporterà istanziazione di DeferInstantiation<T> e DeferInstantiation<F>>, e quindi di T, e di F.

Per eseguire una scelta compiletime su quale o due o più modelli sono istanziata, il linguaggio fornisce specializzazione (come appena illustrato nella definizione di std::conditional<B,T,F> stesso); fornisce il modello di funzione sovraccarico risoluzione e fornisce SFINAE. Specializzazione e sovraccarico di risoluzione può ogni essere sinergicamente sfruttato con SFINAE, attraverso il supporto delle librerie di std::enable_if<B,T>

Il problema che vi ha ostacolato nella realizzazione particolare ricorsiva meta-funzione che si desidera non è uno di scegliere tra data tipi ma di scegliere il modello nel quale dirigere l'istanza ricorsiva. std::conditional non è per lo scopo. @ La risposta di Pradhan dimostra che un modello diverso da std::conditional può essere scritto per effettuare una scelta compositime tra due modelli , senza che implica che entrambi devono essere istanziati. Applica la specializzazione per farlo.

Come dici tu, hai già trovato una soluzione di specializzazione per il problema . Questo è in linea di principio il modo giusto per controllare ricorsivamente la selezione del modello in meta-funzioni ricorsive. Tuttavia, con l'avvento di constexpr, le meta-funzioni ricorsive non comandano nulla come la quota di mercato dei problemi che facevano in precedenza, e la maggior parte del mal di cervello che hanno causato è un ricordo del passato.

Il problema particolare qui - determinare in compiletime se una stringa è una stringa di un altro - possono essere risolti senza prese con template meta-programmazione, e senza rappresentare stringhe compiletime altrimenti che come tradizionali stringhe letterali:

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

Questo verrà compilato come C++ 11 o superiore.

Si può anche avere ragioni per desiderare di rappresentare le stringhe compiletime come CharList<char ...> diversa rendendoli suscettibili di query TMP compiletime come questo. Possiamo vedere che CharList<char ...Cs> ha una statica membro costante size valutazione di sizeof...(Cs) ed ha una funzione at<N>() membro static valutare la N esimo del ...Cs. In tal caso (assumendo che at<N>() è debug), si potrebbe adattare is_substr di essere una funzione template aspetta CharList<char ...> parametri all'incirca le seguenti righe:

#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>(); 
} 

che illustra l'applicazione di SFINAE, leveraged by std::enable_if

Infine, si potrebbe anche essere interessato a questo programma:

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

che stampa:

Hello World 
12 
H 

(codice compilato con g ++ 4.9, clang 3.5)