8

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")?

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    I don't think you can make that work with multiple inheritance. The problem being that it isn't the declaration of `dup_helper` which causes an error but its definition (if I'm not mistaken). – Caninonos Aug 14 '15 at 19:03
  • @Caninonos In theory, Vittorio was instantiation a `T` in `decltype(T{})` then getting the type. Vittorio was hoping that the `T{}` would generate an error, because the actual statement `T{}` is an error, even if the type resulting is not. – Yakk - Adam Nevraumont Aug 14 '15 at 19:32
  • @Yakk but `T{}` requires the compiler to define `T` and the error is inside its definition. It's a bit like trying having an error happening in the body of a templated function (instead of its trailing return type/noexcept specifier/default template argument) and trying to "catch" it with sfinae, I don't think it is possible (I don't know exactly what the standard says but still). (also, just to post a "solution" with multiple inheritance, there's [this](http://ideone.com/oT20iI); it's completely unreliable though as it relies on EBO and the size of an empty class, so don't use it) – Caninonos Aug 14 '15 at 19:47
  • 2
    Apart from the use of `void_t`, this is the same as http://stackoverflow.com/questions/24053998/detect-same-class-inheritance-with-sfinae?rq=1 – ecatmur Aug 14 '15 at 20:17

2 Answers2

7

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.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • There doesn't seem to be good "immediate context" primers on the web (outside of copies of the standard). Do you have a decoding of the standard that explains why this is "outside the immediate context"? – Yakk - Adam Nevraumont Aug 14 '15 at 19:55
  • 1
    @Yakk my rule of thumb is that if you can point to a language-level operation occurring within the SFINAE-expression whose operands are well-formed but that is nevertheless invalid, then you're OK; if the invalid language-level operation is outside the SFINAE-expression then it won't work. – ecatmur Aug 14 '15 at 20:11
  • `std::void_t>(inherit...>{}))...)>>` What is `void(...)` doing there wrapping the whole thing into a function signature, if void_t is variadic anyways? – brunocodutra Sep 29 '15 at 20:58
  • Needless to say ecatmur's example is rejected by MSVC 14, but fortunately [there is hope](http://goo.gl/UtqM5S). This modified version of ecatmur's example was verified to compile on MSVC 14 (19.00.23106.0) using microsoft's [online compiler](http://webcompiler.cloudapp.net/). – brunocodutra Sep 29 '15 at 21:18
  • @brunocodutra `void(...)` - oops, I must have forgot that `void_t` is variadic. Nice work on adapting it to MSVC. – ecatmur Sep 30 '15 at 10:04
0

That's my solution using meta-programming and type-list idiom. I use this code as part of my library implementing reflection for C++. I think there is no need in void_t or inheritance at all to solve this task.

template <typename ...Args>
struct type_list
{};

using empty_list = type_list<>;

// identity
template<typename T>
struct identity
{
    using type = T;
};

// is_typelist
template<typename T>
struct is_typelist: std::false_type
{};

template<typename ...Args>
struct is_typelist<type_list<Args...>>: std::true_type
{};

template<typename T>
struct check_typelist
{
    using type = void;
    static constexpr void *value = nullptr;
    static_assert(is_typelist<T>::value, "T is not a type_list!");
};

// indexof
namespace internal {

template<typename T, typename V, std::int64_t index>
struct typelist_indexof_helper: check_typelist<T>
{};

template<typename H, typename ...T, typename V, std::int64_t index>
struct typelist_indexof_helper<type_list<H, T...>, V, index>:
        std::conditional<std::is_same<H, V>::value,
            std::integral_constant<std::int64_t, index>,
            typelist_indexof_helper<type_list<T...>, V, index + 1>
        >::type
{};

template<typename V, std::int64_t index>
struct typelist_indexof_helper<empty_list, V, index>: std::integral_constant<std::int64_t, -1>
{};

}

template<typename T, typename V>
using typelist_indexof = ::internal::typelist_indexof_helper<T, V, 0>;

template<typename T, typename V>
struct typelist_exists: std::integral_constant<bool, typelist_indexof<T, V>::value >= 0>
{};

// remove_duplicates
namespace internal {

template<typename P, typename T>
struct typelist_remove_duplicates_helper: check_typelist<T>
{};

template<typename ...P, typename H, typename ...T>
struct typelist_remove_duplicates_helper<type_list<P...>, type_list<H, T...>>:
        std::conditional<typelist_exists<type_list<T...>, H>::value,
            typelist_remove_duplicates_helper<type_list<P...>, type_list<T...>>,
            typelist_remove_duplicates_helper<type_list<P..., H>, type_list<T...>>
        >::type
{};

template<typename ...P>
struct typelist_remove_duplicates_helper<type_list<P...>, empty_list>: identity<type_list<P...>>
{};

}

template<typename T>
using typelist_remove_duplicates = ::internal::typelist_remove_duplicates_helper<empty_list, T>;


template<typename ...Args>
struct has_no_duplicates: std::integral_constant<bool, std::is_same<type_list<Args...>,
                                                                    typename typelist_remove_duplicates<type_list<Args...>>::type>::value>
{};

DEMO

Elohim Meth
  • 1,777
  • 9
  • 13