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 {};
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.
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.
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
replace
zastępuje wszystkie wystąpienia T
z U
w non-rekurencyjne mody.
replace_or_rebind
zastępuje wszystkie wystąpienia T
z U
, a dla wszystkich wystąpień niedopasowanych, wzywa ponownie powiązać
replace_or_rebind_if_not
jest taka sama jak replace_or_rebind
ale pomija jakikolwiek element pasujący Not
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>>;
};
- 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
- 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
.
- Jeśli
T
ma członka rebind
, użyj tego.
- Jeśli
T
nie ma członka rebind
, replace_or_rebind
wszystkie parametry do szablonu C
, które pasują do pierwszego parametru szablonu C
.
- 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
.
- 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.
- 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
.
- Pozwala
rebind
prace nad tablic wielkości c-EX: T[5]
- Lets
rebind
pracę przy C-macierze bez size Ex: T[]
rebind
s lwartości-ref T
rodzajów, aby zagwarantowane lwartość-ref do std::remove_reference_t<U>
.
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 { ... };
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. –
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
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. –