2015-03-24 21 views
6

aktualizacjiJak static_assert grać ładny z SFINAE

Zamieściłem pracuje brulion z rebind jako odpowiedź na pytanie. Chociaż nie miałem szczęścia znaleźć ogólny sposób, aby powstrzymać metamunkcje przed static_assert.


Zasadniczo chcę, aby sprawdzić, czy na matrycy typu T<U, Args...> może być wykonana z innego rodzaju T<V, Args...>. Gdzie T i Args... jest taki sam w obu typach. Problem polega na tym, że T<> może mieć w sobie static_assert, który całkowicie niszczy moją metafunkcję.

Poniżej znajduje się przybliżone podsumowanie tego, co próbuję zrobić.

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!" 
); 

Jak widać pożądane zachowanie jest, że ostateczna static_assert powinien przejść, ale niestety, to nawet nie dostać się do tego punktu jako static_assert w fake_cont jest wyzwalany, gdy std::is_constructible<> próbuje wywołać konstruktor fake_cont „s .

W prawdziwym kodzie fake_cont jest libC++ 's std::vector, więc nie mogę zmodyfikować jego wnętrzności lub jelit std::is_constructible.

Wszelkie porady dotyczące obchodzenia się z tą konkretną kwestią są mile widziane, a wszelkie ogólne porady dotyczące SFINAE'u w okolicy static_assert są szczególnie doceniane.

Edycja: pierwsza część is_same powinien być fake_cont<int, fake_alloc<int>>

Edit 2: Jeśli zakomentuj static_assert w fake_cont, kompiluje szczęk (3.5). I tego właśnie chcę. Więc potrzebuję jakiegoś sposobu, aby uniknąć static_assert w fake_cont.

+0

Twoje pytanie jest niejasne, jako przykład dajesz nigdy pracować. Masz na myśli 'sample_rebind , int>'? I nawet to spowodowałoby 'fake_cont >' jeśli trzymasz 'OtherArgs ...'. W obecnej formie kompilator jest po prostu poprawny, aby odrzucić twój kod. –

+0

Skomentowałem 'static_assert' w' fake_cont', a on faktycznie skompilował (przynajmniej na clang 3.5), przekazując final static_assert. Nie mówię, że kompilator jest zły, po prostu pytam, w jaki sposób mogę sprawić, aby kompilator nie zawiódł w części "is_constructible" specjalizacji. Zauważ, że jeśli typ nie jest constuctible, 'sample_rebind' zwraca oryginalny typ – xcvr

+0

Przekazuje, ponieważ użyłeś' using type = T; 'w nie specjalistycznym przypadku (który jest następnie brany pod uwagę). Po prostu połączyłeś trzy błędy, aby przejść! Usuń którekolwiek z nich, a to się nie powiedzie. –

Odpowiedz

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

Następnie złóż dodatkowy test w:

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

i piszemy dodatkowego testu:

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

w zasadzie, to pozwala nam wstrzyknąć "łaty" na naszym teście, aby dopasować static_assert.

Gdybyśmy mieli is_std_container<T> i get_allocator<T>, moglibyśmy napisać:

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

lub może po prostu stwierdzić, że coś z allocator_type prawdopodobnie nie może być odbicie.

Bardziej pojemnik świadome podejście do tego problemu byłoby wyodrębnić typ przydzielania (::allocator_type) i zastąpić wszystkie wystąpienia typu przydzielania w liście argumentów pojemnika z ponownego wiązania T do U jakoś. Jest to nadal trudne, ponieważ std::map<int, int> ma podzielnik typu std::allocator< std::pair<const int, int> >, a rozróżnianie między kluczem int a wartością int nie jest możliwe w sposób ogólny.

+0

Komentarz na temat zamiany ':: allocator_type' jest świetnym pomysłem, a twoje ostrzeżenie o mapie wygląda na to, że można ją obejść (poprzez specjalizację na' allocator_template < std :: pair/tuple <...>> '). Ale 'std :: vector >' złamie to. Spróbuję nad tym pracować w ten weekend. – xcvr

1

Udało mi się uzyskać całkiem niezły pierwszy projekt ponownego wiązania. Działa dla wszystkich kontenerów STL (z pominięciem mniej popularnych kombinacji parametrów szablonu), adapterów kontenerów i std::integer_sequence. I prawdopodobnie działa również na wiele innych rzeczy. Ale na pewno nie zadziała na wszystko.

Głównym problemem było sprawienie, by typy mapowe działały tak, jak przewidywał Yakk, ale mała cecha typowa pomogła im w tym.

tak w kodzie ...

void_t

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

Ten mały trick Walter E. Brown sprawia wykonawczych typu cech dużo łatwiejsze.

typ cech

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 wykorzystuje fakt, że przy użyciu mapy, jak typy w STL posiadają w value_type określa się jako (n) std::pair z const ed pierwszy parametr Wzór typu mapowego, będącego first_type w . Drugi parametr szablonu typu podobnego do mapy pasuje dokładnie do pair 's second_type. rebind musi ostrożniej obsługiwać typy map.
  2. has_mem_rebind wykrywa obecność członka rebind meta-funkcja na T przy użyciu sztuczki void_t. Jeśli klasa ma numer rebind, najpierw odkładamy implementację klas.
  3. is_template_instantiation wykrywa, czy typ T jest instancją szablonu. To jest bardziej do debugowania.

Lista Helper Rodzaj

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

ten obsługuje pewną prostą listę jak manipulacje typów

  1. replace zastępuje wszystkie wystąpienia T z U w non-rekurencyjne mody.
  2. replace_or_rebind zastępuje wszystkie wystąpienia T z U, a dla wszystkich wystąpień niedopasowanych, wzywa ponownie powiązać
  3. replace_or_rebind_if_not jest taka sama jak replace_or_rebind ale pomija jakikolwiek element pasujący Not
  4. push_front prostu popycha element do przodu od typu listy

Wywołanie Użytkownik Przypisz

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

Okazuje się, że istnieją dwa różne ważne sposoby implementacji elementu rebind zgodnie ze standardem. Dla allocators jest to rebind<T>::other. Dla pointers jest to tylko rebind<T>. Ta implementacja do_mem_rebind jest zgodna z rebind<T>::other, jeśli istnieje, w przeciwnym razie powraca do prostszego rebind<T>.

Rozpakowanie

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; 

To trwa pack, wyciągi typy zawiera, i umieszcza je w jakiś inny szablon C.

Rebind Realizacja

dobre rzeczy.

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. Sprawa nie dla rebind jest po prostu zostawić typ niezmienione. Pozwala to zadzwonić pod numer rebind<Types, double>..., nie martwiąc się o to, czy każda z nich jest w stanie obsłużyć. Tam jest static_assert na wypadek otrzymania instancji szablonu. Jeśli tak się stanie, prawdopodobnie potrzebujesz innej specjalizacji: rebind
  2. Wygląda na to, że mapa podobna do rebind będzie wywoływana jak rebind<std::map<int, int>, std::pair<double, std::string>>. Zatem typ, do którego przydzielany jest alokator, nie jest dokładnie taki, jak typ, do którego kontener jest odbijany. Działa on we wszystkich typach z wyjątkiem typów Key i Value, przy czym if_not to . Ponieważ typ podzielnika różni się od pary klucz/wartość rebind, należy zmodyfikować wartość pierwszego elementu pary. Używa ona std::allocator_traits do ponownego powiązania alokatora, ponieważ wszystkie alokatory muszą być ponownie wiązane przez std::allocator_traits.
  3. Jeśli T ma członka rebind, użyj tego.
  4. Jeśli T nie ma członka rebind, replace_or_rebind wszystkie parametry do szablonu C, które pasują do pierwszego parametru szablonu C.
  5. Jeśli T ma jeden parametr typu i kilka nie szablonowych parametrów szablonu, których typ odpowiada temu parametrowi. Próba przekształcenia wszystkich tych parametrów niepoprawnych na U. W takim przypadku działa std::integer_sequence.
  6. Wymagany był specjalny przypadek dla std::array, ponieważ pobiera on parametr szablonu non-type, podając jego rozmiar, a ten parametr szablonu powinien pozostać sam.
  7. Przypadek ten umożliwia ponowne powiązanie wskaźników z innymi typami wskaźników. Aby to osiągnąć, używa on std::pointer_traits 's rebind.
  8. Pozwala rebind prace nad tablic wielkości c-EX: T[5]
  9. Lets rebind pracę przy C-macierze bez size Ex: T[]
  10. rebind s lwartości-ref T rodzajów, aby zagwarantowane lwartość-ref do std::remove_reference_t<U>.
  11. rebind s rvalue-ref T typy z gwarancją rvalue-ref na std::remove_reference_t<U>.

Pochodzące (Exposed) Klasa

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; 

Początek SFINAE i static_assert

Po dużo googling nie wydaje się być to ogólny sposób SFINAE całym static_assert Like te w kontenerach STL libC++. To naprawdę sprawia, że ​​chciałbym, żeby język miał coś bardziej przyjaznego SFINAE, ale trochę więcej ad-hoc niż koncepcji.

odczuwalna:

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