I managed to get a pretty solid first search option. It works for all STL containers (disallowing less common combinations of template parameters), adapter containers, and std::integer_sequence . And that probably works for more things. But that certainly will not work for everything.
The main problem was that types like the map worked as predicted by Yakk, but a little bit of characterization helped.
So, to the code ...
void_t
template<class...> using void_t = void;
This little trick of Walter E. Brown greatly simplifies the implementation of type traits.
Typical features
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 advantage of the fact that display types like in STL have value_type defined as (n) std::pair , with the first template parameter const ed for the map type, first_type in pair . The second parameter of the map type template matches exactly pair second_type . rebind should be more careful with card types.has_mem_rebind detects the presence of a rebind member meta function on T using the void_t trick. If a class has rebind , then we will first consider the implementation of classes.is_template_instantiation determines whether type T instance of a template. This is more for debugging.
List of Helper Types
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...>; };
This handles some simple list like type manipulation
replace replaces all occurrences of T with U non-recursive manner.replace_or_rebind replaces all occurrences of T with U , and for all inappropriate occurrences, calls to rebindreplace_or_rebind_if_not same as replace_or_rebind , but skips any element matching Notpush_front just pushes an element to the front of the type list
Call 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; };
It turns out that there are two different valid ways to implement the rebind element in accordance with the standard. For dispensers, it rebind<T>::other . For pointers, it's just rebind<T> . This do_mem_rebind implementation comes with rebind<T>::other if it exists, otherwise it returns to a simpler rebind<T> .
Unpacking
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;
This takes a pack , extracts the types it contains, and puts them in some other C template.
Reinvest implementation
Good material.
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>>; };
- The error for
rebind is to simply leave the type unchanged. This allows you to call rebind<Types, double>... without worrying about whether each Type in Types rebind . There is static_assert if it receives an instance of the template. If this hits, you probably need another rebind specialization - It is assumed that map-like
rebind will be called as rebind<std::map<int, int>, std::pair<double, std::string>> . Thus, the type on which the dispenser is dropped does not exactly match the type on which the container is being restored. It performs replace_or_rebind_if_not for all types except key and value types, with if_not being allocator_type . Since the type of dispenser is different from the key / rebind , you must change the const value of the first element of the pair. It uses std::allocator_traits to re-bind the distributor, since all distributors must be rearranged through std::allocator_traits . - If
T has a rebind member, use it. - If
T does not have a rebind element, replace_or_rebind all the parameters to the C pattern that match the parameter of the first C pattern. - If
T has one type parameter and a set of nonpig template parameters, the type of which corresponds to this parameter. Try redoing all those off-peak parameters to U This does the work of std::integer_sequence . - A special case was required for
std::array , since a non-piggy template parameter was set for it, and this template parameter should be left alone. - This case allows you to restore pointer bindings to other types of pointers. To do this, use
std::pointer_traits rebind . - Allows
rebind to work with ex: T[5] c-array sizes T[5] - Allows
rebind to work with c-arrays without ex: T[] size T[] rebind lvalue-ref T types with a guaranteed value of lvalue-ref to std::remove_reference_t<U> .rebind rvalue-ref T types with a guaranteed value of rvalue-ref to std::remove_reference_t<U> .
Derived (open) class
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 and static_assert
After long searches, there seems to be no common path to SFINAE around static_assert , as in libC ++ STL containers. It really makes me want the language to have something more than SFINAE, but a bit more ad-hoc than concepts.
how
template<class T> static_assert(CACHE_LINE_SIZE == 64, "") struct my_struct { ... };