Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using `void_t` to detect multiple inheritance type repetition errors

I want to implement a has_no_duplicates<...> type trait that evaluates to std::true_type if the passed variadic type list has no duplicate types.

static_assert(has_no_duplicates<int, float>{}, "");
static_assert(!has_no_duplicates<float, float>{}, "");

Let's assume, for the scope of this question, that I want to do that using multiple inheritance.

When a class inherits from the same type more than once, an error occurs.

template<class T> 
struct type { };

template<class... Ts>
struct dup_helper : type<Ts>... { };

// No errors, compiles properly.
dup_helper<int, float> ok{};

// Compile-time error: 
// base class 'type<float>' specified more than once as a direct base class
dup_helper<float, float> error{};

I assumed I could've used void_t to "detect" this error, but I couldn't implement a working solution following the code samples from cppreference.

This is what I tried:

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

// First try:
template<class T>
struct is_valid<T, std::void_t<decltype(T{})>> 
    : std::true_type { };

// Second try:
template<class T>
struct is_valid<T, std::void_t<T>> 
    : std::true_type { };

For my third try, I tried delaying the expansion of dup_helper<...> using a wrapper class that took dup_helper as a template template parameter, like wrapper<dup_helper, ...> and expanded it inside of void_t.

Unfortunately, all my tries resulted in the aforementioned error always preventing compilation.

I assume this type of error is not detectable as a "substitution failure", but I'd like confirmation.


Is this kind of error actually impossible to detect using void_t? (Will it always result in a compilation failure?)

Is there a way to detect it without causing compilation to fail? (Or a non-void_t workaround that still makes use of the "multiple inheritance trick")?

like image 843
Vittorio Romeo Avatar asked Aug 14 '15 18:08

Vittorio Romeo


1 Answers

As @Canoninos noted, the problem is that:

it isn't the declaration of dup_helper<T, T> which causes an error but its definition [...].

Or, in Standardese, the error occurs outside the "immediate context" ([temp.deduct]) of the substitution:

8 - [...] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]

Here the error occurs while instantiating dup_helper<float, float> so is not in the "immediate context".

One multiple inheritance trick that's very close to yours involves adding an extra layer of inheritance, by indexing the multiple bases:

helper<<0, 1>, <float, float>>        
             +            
        +----+----+       
        v         v       
 ix<0, float>  ix<1, float>
        +         +       
        v         v       
     t<float>  t<float>   

This gives us a helper class with a valid definition and that can be instantiated but not cast to its ultimate base classes, because of ambiguity:

static_cast<t<float>>(helper<...>{});  // Error, SFINAE-usable

Example.

like image 92
ecatmur Avatar answered Oct 06 '22 01:10

ecatmur