How to make static_assert play well with SFINAE

Update

I posted a working draft of rebind as an answer to a question. Although I’m not lucky to find a common way to keep static_assert from breaking metafunks.


Basically, I want to check whether it is possible to build a template type T<U, Args...> from another type T<V, Args...> . Where T and Args... same for both types. The problem is that T<> may have static_assert in it, which completely violates my metaphor.

The following is a brief description of what I'm trying to do.

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

As you can see, the desired behavior is that the final static_assert must pass, but unfortunately it does not even reach that point, since static_assert in fake_cont when std::is_constructible<> tries to call fake_cont .

In the actual fake_cont code fake_cont is lib ++ std::vector , so I cannot change its guts or std::is_constructible guts.

Any advice on dealing with this particular problem is welcome, and any advice regarding SFINAE 'around static_assert .

Edit: The first part of is_same should be fake_cont<int, fake_alloc<int>>

Edit 2: if you comment out static_assert in fake_cont , it compiles (clang 3.5). And that is what I want. So I just need to somehow avoid the static_assert in fake_cont .

+6
source share
2 answers
 namespace details { template<class T,class=void> struct extra_test_t: std::true_type {}; } 

Then we reset the additional test to:

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

and write an additional test:

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

Basically, this allows us to introduce β€œpatches” in our test to match static_assert .

If we had is_std_container<T> and get_allocator<T> , we could write:

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

or we could just state that anything with allocator_type probably cannot be restored.

A more understandable approach to this problem for the container is to extract the type of distributor ( ::allocator_type ) and replace all instances of the type of distributor in the container's argument list with a second binding of T to U This is still complicated, since std::map<int, int> has a distributor of the type std::allocator< std::pair<const int, int> > , and the distinction between an int key and an int value is impossible in general.

+1
source

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 rebind
  • replace_or_rebind_if_not same as replace_or_rebind , but skips any element matching Not
  • push_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 { ... }; 
+1
source

Source: https://habr.com/ru/post/984167/


All Articles