2015-08-14 9 views
8

Chcę zaimplementować cechę typu has_no_duplicates<...>, która jest oceniana na std::true_type, jeśli lista typów variadic nie ma duplikatów.Użycie `void_t` do wykrycia błędów wielokrotnego powtarzania typu

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

Załóżmy, dla zakresu tego pytania, że ​​chcę to zrobić przy użyciu wielu dziedziczenia.

Gdy klasa dziedziczy z tego samego typu więcej niż jeden raz, pojawia się błąd.

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

Przypuszczałem Mogłam stosować void_t do „wykrywania” ten błąd, ale nie mogłem realizować roztwór roboczy following the code samples from cppreference.

To co próbowałem:

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

Na moim trzecim razem, próbowałem opóźniając ekspansję dup_helper<...> użyciu klasy otoki, które miały dup_helper jako parametr szablonu szablonu, jak wrapper<dup_helper, ...> i rozszerzoną go wewnątrz void_t .

Niestety, wszystkie moje próby spowodowały, że wspomniany błąd zawsze uniemożliwia kompilację.

Zakładam, że ten typ błędu nie jest wykrywany jako "niepowodzenie substytucji", ale chciałbym potwierdzić.


Jest to rodzaj błędu właściwie niemożliwe do wykrycia za pomocą void_t? (Czy zawsze spowoduje to awarię kompilacji?)

Czy istnieje sposób na wykrycie tego błędu bez kompilacji? (Lub sposób obejścia inny niż void_t, który nadal wykorzystuje "sztuczkę wielokrotnego dziedziczenia")?

+1

Nie sądzę, można zrobić, że praca z wieloma dziedziczenia. Problem polega na tym, że nie jest to deklaracja 'dup_helper ', która powoduje błąd, ale jego definicję (jeśli się nie mylę). – Caninonos

+0

@Caninonos Teoretycznie Vittorio było instancją 'T' w' decltype (T {}), a następnie otrzymywało typ. Vittorio miał nadzieję, że 'T {}' wygeneruje błąd, ponieważ faktyczna instrukcja 'T {}' jest błędem, nawet jeśli wynikowy typ nie jest. – Yakk

+0

@Aakk, ale 'T {}' wymaga, aby kompilator zdefiniował 'T', a błąd znajduje się w jego definicji. To trochę tak, jak próbowanie błędu występującego w ciele funkcji szablonowej (zamiast końcowego argumentu typ/noexcept specifier/default template) i próby "złapania" go za pomocą sfinae, nie sądzę, że jest to możliwe (Nie wiem dokładnie, co mówi norma, ale nadal). (również, aby opublikować "rozwiązanie" z wieloma dziedziczeniami, istnieje [to] (http://ideone.com/oT20iI), jest to całkowicie niewiarygodne, ponieważ opiera się na EBO i rozmiarze pustej klasy, więc nie " t use it) – Caninonos

Odpowiedz

6

Jak zauważył @Canoninos, problemem jest to, że:

nie jest to deklaracja dup_helper<T, T> która powoduje błąd, ale jego definicja [...].

Albo w Standardese, błąd występuje poza "bezpośredniego związku" ([temp.deduct]) substytucji:

8 - [...] Tylko nieprawidłowe typy i wyrażenia w danym kontekście bezpośrednim kontekście typu funkcji i jego typy parametrów szablonu mogą spowodować błąd dedukcji. [Uwaga: Ocenę podstawionych typów i ekspresji może powodować działania niepożądane, takie jak instancji specjalizacji klasy matrycy i/lub szablon funkcji specjalizacji generowanie pośrednio określonych funkcji, itp efekty uboczne są nie w "bezpośrednim kontekście" i mogą spowodować, że program będzie źle sformułowany.- koniec uwaga]

Tutaj błąd występuje podczas uruchamianiudup_helper<float, float> tak nie jest w „bezpośrednim kontekście”.

Jeden wielokrotne dziedziczenie trik, który znajduje się bardzo blisko ciebie polega na dodaniu dodatkowej warstwy dziedziczenia przez indeksowania wielu baz:

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

To daje nam klasy pomocnika z ważnej definicji i które mogą być tworzone wystąpienia, ale nie są rzucane na najwyższe klasy bazowe z powodu niejasności:

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

Example.

+0

. t wydają się być dobrymi "bezpośrednim kontekstem" starterów w Internecie (poza kopiami standardu). Czy masz dekodowanie standardu, które wyjaśnia, dlaczego jest to "poza bezpośrednim kontekstem"? – Yakk

+1

@ Moja zasada jest taka, że ​​jeśli możesz wskazać operację na poziomie języka występującą w wyrażeniu SFINAE, którego operandy są dobrze sformułowane, ale to jest nieważne, jesteś w porządku; jeśli nieprawidłowa operacja na poziomie języka znajduje się poza wyrażeniem SFINAE, to nie zadziała. – ecatmur

+0

'std :: void_t > (dziedziczenie ...> {})) ...) >>' Co to jest "void (...)", który powoduje zawijanie Cała rzecz w podpis funkcji, jeśli void_t jest tak czy inaczej wariancki? – brunocodutra

0

To jest moje rozwiązanie przy użyciu meta-programowania i idiomu typu listy. Używam tego kodu jako część mojej biblioteki implementującej odbicie dla C++. Myślę, że w ogóle nie ma potrzeby w void_t lub dziedziczenia, aby rozwiązać to zadanie.

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

Powiązane problemy