2015-03-24 11 views
6

AggiornamentoCome fare static_assert bel gioco con SFINAE

Ho pubblicato una bozza di lavoro del rebind come una risposta alla domanda. Anche se non ho avuto molta fortuna a trovare un modo generico per mantenere static_assert s da rompere i metafunzioni.


Fondamentalmente voglio verificare se un tipo su modelli T<U, Args...> può essere costruito da qualche altro tipo T<V, Args...>. Dove T e Args... è lo stesso in entrambi i tipi. Il problema è, T<> potrebbe avere un static_assert in esso che interrompe totalmente la mia metafunzione.

Di seguito è riportato un sommario di ciò che sto cercando di fare.

template<typename T> 
struct fake_alloc { 
    using value_type = T; 
}; 

template<typename T, typename Alloc = fake_alloc<T>> 
struct fake_cont { 
    using value_type = T; 
    // comment the line below out, and it compiles, how can I get it to compile without commenting this out??? 
    static_assert(std::is_same<value_type, typename Alloc::value_type>::value, "must be the same type"); 
}; 

template<typename T, typename U, typename = void> 
struct sample_rebind { 
    using type = T; 
}; 

template<template<typename...> class Container, typename T, typename U, typename... OtherArgs> 
struct sample_rebind< 
    Container<T, OtherArgs...>, 
    U, 
    std::enable_if_t< 
     std::is_constructible< 
      Container<T, OtherArgs...>, 
      Container<U, OtherArgs...> 
     >::value 
    > 
> 
{ 
    using type = Container<U, OtherArgs...>; 
}; 

static_assert(
    std::is_same< 
     fake_cont<int, fake_alloc<int>>, 
     typename sample_rebind<fake_cont<int>, double>::type 
    >::value, 
    "This should pass!" 
); 

Come si può vedere il comportamento desiderato è che la finale static_assert dovrebbe passare, ma purtroppo, non ha nemmeno arrivare a quel punto come il static_assert in fake_cont viene attivato quando std::is_constructible<> tentativi di chiamata al costruttore fake_cont s' .

Nel codice reale fake_cont è libC++ 's std::vector, quindi non posso modificare il suo coraggio, o il coraggio di std::is_constructible.

Qualsiasi consiglio su come aggirare questo problema specifico è apprezzato e qualsiasi consiglio in generale per SFINAEing intorno a static_assert è particolarmente apprezzato.

Edit: la prima parte del is_same avrebbe dovuto essere fake_cont<int, fake_alloc<int>>

Edit 2: se si commento la static_assert in fake_cont, compila (clang 3.5). Ed è quello che voglio. Quindi ho solo bisogno di un modo per evitare il static_assert in fake_cont.

+0

La tua domanda non è chiara come l'esempio si dà non avrebbe mai funzionato. Intendi 'sample_rebind , int>'? E anche quello risulterebbe in 'fake_cont >' se si mantiene 'OtherArgs ...'. Allo stato attuale, il compilatore è semplicemente corretto per rifiutare il codice. –

+0

Ho commentato 'static_assert' in' fake_cont', e in effetti compila (almeno su clang 3.5), passando il static_assert finale. Non sto dicendo che il compilatore è sbagliato, sto solo chiedendo come posso fare in modo che il compilatore non fallisca nella parte 'is_constructible' della specializzazione. Nota che se il tipo non è constucibile 'sample_rebind' restituisce il tipo originale – xcvr

+0

Passa perché hai usato' using type = T; 'nel caso non specializzato (che viene poi preso). Hai appena combinato tre errori per farlo passare! Rimuovere qualcuno di loro e fallirà. –

risposta

1
namespace details { 
    template<class T,class=void> 
    struct extra_test_t: std::true_type {}; 
} 

Abbiamo poi piegare un test supplementare in:

template<class...>struct types{using type=types;}; 

template<template<typename...> class Container, typename T, typename U, typename... OtherArgs> 
struct sample_rebind< 
    Container<T, OtherArgs...>, 
    U, 
    std::enable_if_t< 
    details::extra_test_t< types< Container<T, OtherArgs...>, U > >::value 
    && std::is_constructible< 
     Container<T, OtherArgs...>, 
     Container<U, OtherArgs...> 
    >::value 
    > 
> { 
    using type = Container<U, OtherArgs...>; 
}; 

e scriviamo il test in più:

namespace details { 
    template<class T, class Alloc, class U> 
    struct extra_test_t< 
    types<std::vector<T,Alloc>, U>, 
    typename std::enable_if< 
     std::is_same<value_type, typename Alloc::value_type>::value 
    >::type 
    > : std::true_type {}; 
    template<class T, class Alloc, class U> 
    struct extra_test_t< 
    types<std::vector<T,Alloc>, U>, 
    typename std::enable_if< 
     !std::is_same<value_type, typename Alloc::value_type>::value 
    >::type 
    > : std::false_type {}; 
} 

Fondamentalmente, questo ci permette di iniettare "patch" a nostro test per abbinare il static_assert.

Se avessimo is_std_container<T> e get_allocator<T>, potremmo scrivere:

namespace details { 
    template<template<class...>class Z,class T, class...Other, class U> 
    struct extra_test_t< 
    types<Z<T,Other...>>, U>, 
    typename std::enable_if< 
     is_std_container<Z<T,Other...>>>::value 
     && std::is_same< 
     value_type, 
     typename get_allocator<Z<T,Other...>>::value_type 
     >::value 
    >::type 
    > : std::true_type {}; 
    template<class T, class Alloc, class U> 
    struct extra_test_t< 
    types<std::vector<T,Alloc>, U>, 
    typename std::enable_if< 
     is_std_container<Z<T,Other...>>>::value 
     && !std::is_same< 
     value_type, 
     typename get_allocator<Z<T,Other...>>::value_type 
     >::value 
    >::type 
    > : std::false_type {}; 
} 

o potremmo semplicemente affermare che qualsiasi cosa con un allocator_type probabilmente non può essere rimbalzo.

Un altro contenitore-aware approccio a questo problema sarebbe quello di estrarre il tipo di allocatore (::allocator_type), e sostituire tutte le istanze del tipo allocatore nella lista degli argomenti contenitore con un Rebind di T-U in qualche modo. Ciò è ancora complesso, poiché std::map<int, int> ha un allocatore di tipo std::allocator< std::pair<const int, int> > e la distinzione tra la chiave int e il valore int non è possibile in modo generico.

+0

Il commento sulla sostituzione di ':: allocator_type' è una grande idea, e il tuo avviso sulla mappa sembra che possa essere aggirato (specializzandosi su' allocator_template < std :: pair/tuple <...>> '). Ma 'std :: vector >' romperebbe questo. Cercherò di lavorarci su questo fine settimana. – xcvr

1

Sono riuscito a ottenere una prima bozza piuttosto solida di rebind in corso. Funziona con tutti i contenitori STL (tranne le combinazioni meno comuni di parametri modello), gli adattatori contenitore e std::integer_sequence. E probabilmente funziona anche per molte altre cose. Ma certamente non funzionerà per tutto.

Il guaio principale era far sì che i tipi di mappa funzionassero come previsto da Yakk, ma un piccolo tratto di carattere ha contribuito a questo.

così via per il codice ...

void_t

template<class...> 
using void_t = void; 

Questo piccolo trucco di Walter E. Brown rende tipo attuazione Tratti molto più facile.

caratteri morfologici

template<class T, class = void> 
struct is_map_like : std::false_type {}; 

template<template<class...> class C, class First, class Second, class... Others> 
struct is_map_like<C<First, Second, Others...>, 
        std::enable_if_t<std::is_same<typename C<First, Second, Others...>::value_type::first_type, 
               std::add_const_t<First>>{} && 
            std::is_same<typename C<First, Second, Others...>::value_type::second_type, 
               Second>{}>> 
    : std::true_type {}; 

template<class T, class U, class = void> 
struct has_mem_rebind : std::false_type {}; 

template<class T, class U> 
struct has_mem_rebind<T, U, void_t<typename T::template rebind<U>>> : std::true_type {}; 

template<class T> 
struct is_template_instantiation : std::false_type {}; 

template<template<class...> class C, class... Others> 
struct is_template_instantiation<C<Others...>> : std::true_type {}; 
  1. is_map_like usa il fatto che la mappa-come tipi nella STL tutti hanno value_type definito per essere un (n) std::pair con il const Ed primo parametro modello del tipo mappa, essendo lo first_type nello pair. Il secondo parametro template del tipo map-like corrisponde esattamente allo second_type dello pair. rebind deve gestire con maggiore attenzione i tipi di mappa.
  2. has_mem_rebind rileva la presenza di un membro rebind meta-funzione su T utilizzando il trucco void_t. Se una classe ha rebind, prima rimanderemo all'implementazione delle classi.
  3. is_template_instantiation rileva se il tipo T è un'istanza di modello. Questo è più per il debug.

Helper Tipo Lista

template<class... Types> 
struct pack 
{ 
    template<class T, class U> 
    using replace = pack< 
     std::conditional_t< 
      std::is_same<Types, T>{}, 
      U, 
      Types 
     >... 
    >; 
    template<class T, class U> 
    using replace_or_rebind = pack< 
     std::conditional_t< 
      std::is_same<Types, T>{}, 
      U, 
      typename rebind<Types, U>::type 
     >... 
    >; 
    template<class Not, class T, class U> 
    using replace_or_rebind_if_not = pack< 
     std::conditional_t< 
      std::is_same<Types, Not>{}, 
      Types, 
      std::conditional_t< 
       std::is_same<Types, T>{}, 
       U, 
       typename rebind<Types, U>::type 
      > 
     >... 
    >; 

    template<class T> 
    using push_front = pack<T, Types...>; 
}; 

Questo gestisce alcune elenco semplice come manipolazioni di tipo

  1. replace sostituisce tutte le occorrenze di T con U in modo non ricorsivo.
  2. replace_or_rebind sostituisce tutte le occorrenze di T con U, e per tutte le occorrenze non corrispondenti, chiamate Rebind
  3. replace_or_rebind_if_not è uguale replace_or_rebind ma salta su ogni elemento corrispondente Not
  4. push_front spinge semplicemente un elemento al fronte del tipo list

Calling Stati Rebind

// has member rebind implemented as alias 
template<class T, class U, class = void> 
struct do_mem_rebind 
{ 
    using type = typename T::template rebind<U>; 
}; 

// has member rebind implemented as rebind::other 
template<class T, class U> 
struct do_mem_rebind<T, U, void_t<typename T::template rebind<U>::other>> 
{ 
    using type = typename T::template rebind<U>::other; 
}; 

Si scopre che esistono due diversi modi validi per implementare un membro rebind in base allo standard. Per allocators è rebind<T>::other. Per pointers è solo rebind<T>. Questa implementazione di do_mem_rebind va con rebind<T>::other se esiste, altrimenti torna al più semplice rebind<T>.

disimballaggio

template<template<class...> class C, class Pack> 
struct unpack; 

template<template<class...> class C, class... Args> 
struct unpack<C, pack<Args...>> { using type = C<Args...>; }; 

template<template<class...> class C, class Pack> 
using unpack_t = typename unpack<C, Pack>::type; 

questo richiede un pack, estrae i tipi in esso contenuti, e li mette in qualche altro modello C.

Rebind Attuazione

La roba buona.

template<class T, class U, bool = is_map_like<T>{}, bool = std::is_lvalue_reference<T>{}, bool = std::is_rvalue_reference<T>{}, bool = has_mem_rebind<T, U>{}> 
struct rebind_impl 
{ 
    static_assert(!is_template_instantiation<T>{}, "Sorry. Rebind is not completely implemented."); 
    using type = T; 
}; 

// map-like container 
template<class U, template<class...> class C, class First, class Second, class... Others> 
class rebind_impl<C<First, Second, Others...>, U, true, false, false, false> 
{ 
    using container_type = C<First, Second, Others...>; 
    using value_type = typename container_type::value_type; 
    using old_alloc_type = typename container_type::allocator_type; 

    using other_replaced = typename pack<Others...>::template replace_or_rebind_if_not<old_alloc_type, First, typename U::first_type>; 

    using new_alloc_type = typename std::allocator_traits<old_alloc_type>::template rebind_alloc<std::pair<std::add_const_t<typename U::first_type>, typename U::second_type>>; 
    using replaced = typename other_replaced::template replace<old_alloc_type, new_alloc_type>; 

    using tail = typename replaced::template push_front<typename U::second_type>; 
public: 
    using type = unpack_t<C, typename tail::template push_front<typename U::first_type>>; 
}; 

// has member rebind 
template<class T, class U> 
struct rebind_impl<T, U, false, false, false, true> 
{ 
    using type = typename do_mem_rebind<T, U>::type; 
}; 

// has nothing, try rebind anyway 
template<template<class...> class C, class T, class U, class... Others> 
class rebind_impl<C<T, Others...>, U, false, false, false, false> 
{ 
    using tail = typename pack<Others...>::template replace_or_rebind<T, U>; 
public: 
    using type = unpack_t<C, typename tail::template push_front<U>>; 
}; 

// has nothing, try rebind anyway, including casting NonType template parameters 
template<class T, template<class, T...> class C, class U, T FirstNonType, T... Others> 
struct rebind_impl<C<T, FirstNonType, Others...>, U, false, false, false, false> 
{ 
    using type = C<U, U(FirstNonType), U(Others)...>; 
}; 

// array takes a non-type parameter parameters 
template<class T, class U, std::size_t Size> 
struct rebind_impl<std::array<T, Size>, U, false, false, false, false> 
{ 
    using type = std::array<U, Size>; 
}; 

// pointer 
template<class T, class U> 
struct rebind_impl<T*, U, false, false, false, false> 
{ 
    using type = typename std::pointer_traits<T*>::template rebind<U>; 
}; 

// c-array 
template<class T, std::size_t Size, class U> 
struct rebind_impl<T[Size], U, false, false, false, false> 
{ 
    using type = U[Size]; 
}; 

// c-array2 
template<class T, class U> 
struct rebind_impl<T[], U, false, false, false, false> 
{ 
    using type = U[]; 
}; 

// lvalue ref 
template<class T, class U> 
struct rebind_impl<T, U, false, true, false, false> 
{ 
    using type = std::add_lvalue_reference_t<std::remove_reference_t<U>>; 
}; 

// rvalue ref 
template<class T, class U> 
struct rebind_impl<T, U, false, false, true, false> 
{ 
    using type = std::add_rvalue_reference_t<std::remove_reference_t<U>>; 
}; 
  1. Il caso sicuro per rebind è semplicemente lasciare invariato il tipo. Ciò consente di chiamare rebind<Types, double>... senza doversi preoccupare se ogni Type in Types è in grado di supportare rebind. C'è un static_assert lì nel caso in cui riceve un modello di istanziazione. Se questo è successo, probabilmente hai bisogno di un'altra specializzazione di rebind
  2. La mappa come rebind si aspetta di essere invocata come rebind<std::map<int, int>, std::pair<double, std::string>>. Quindi il tipo a cui si sta eseguendo il rimpiazzo dell' allocatore non corrisponde esattamente al tipo di rimbalzo del contenitore. Fa un replace_or_rebind_if_not su tutti i tipi tranne i tipi Key e Value, con if_notallocator_type. Poiché il tipo di allocatore differisce dalla coppia chiave/valore rebind, è necessario modificare il valore const del primo elemento della coppia. Utilizza std::allocator_traits per riassociare l'allocatore, poiché tutti gli allocatori devono essere riconciliabili tramite std::allocator_traits.
  3. Se T ha un membro rebind, utilizzare quello.
  4. Se T non ha alcun membro rebind, replace_or_rebind tutti i parametri del modello C corrispondenti al primo parametro del modello C.
  5. Se T ha un parametro di tipo e un gruppo di parametri di modello non di tipo il cui tipo corrisponde a tale parametro. Tentativo di ripetere tutti questi parametri non di tipo su U. Questo è il caso che fa funzionare std::integer_sequence.
  6. Un caso speciale è stato richiesto per std::array poiché accetta un parametro di modello non di tipo che fornisce le sue dimensioni e tale parametro del modello deve essere lasciato in bianco.
  7. Questo caso consente di riaffilare i puntatori ad altri tipi di puntatore. Utilizza std::pointer_traits 's rebind per realizzare questo.
  8. Lets rebind lavoro sul c-array di dimensioni ex: T[5]
  9. Lets rebind lavoro sul c-array senza una dimensione ex: T[]
  10. rebind s lvalue-ref T tipi ad un lvalue-ref garantito per std::remove_reference_t<U>.
  11. rebind s rvalue-ref T tipi a un valore garantito-ref a std::remove_reference_t<U>.

Derivato (Exposed) Classe

template<class T, class U> 
struct rebind : details::rebind_impl<T, U> {}; 

template<class T, class U> 
using rebind_t = typename rebind<T, U>::type; 

Back To SFINAE e static_assert

Dopo molto googling non sembra essere un modo generico per SFINAE intorno static_assert s come quelli contenuti nei contenitori STL di libC++. Mi fa davvero desiderare che la lingua abbia qualcosa di più amichevole di SFINAE, ma un po 'più ad-hoc dei concetti.

come:

template<class T> 
    static_assert(CACHE_LINE_SIZE == 64, "") 
struct my_struct { ... };