2015-08-14 3 views
8

Я хочу реализовать признак типа has_no_duplicates<...>, который оценивается как std::true_type, если в переданном списке вариационного типа нет повторяющихся типов.Использование `void_t` для обнаружения множественных ошибок повторения типа наследования

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

Предположим, что для сферы применения этого вопроса я хочу сделать это с использованием множественного наследования.

Если класс наследуется от одного и того же типа более одного раза, возникает ошибка.

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

Я предположил, что я мог бы использоваться для void_t «обнаружить» эту ошибку, но я не мог реализовать рабочий раствор following the code samples from cppreference.

Это то, что я пробовал:

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

Для моей третьей попытки, я попытался задерживая расширение dup_helper<...> с помощью класса-оболочки, который взял dup_helper в качестве параметра шаблона шаблона, как wrapper<dup_helper, ...> и расширенного его внутри void_t ,

К сожалению, все мои попытки привели к тому, что вышеупомянутая ошибка всегда предотвращала компиляцию.

Я предполагаю, что этот тип ошибки не обнаруживается как «сбой замены», но я бы хотел подтвердить.


Такую ошибку на самом деле невозможно обнаружить с помощью void_t? (Будет ли это всегда приводить к сбою компиляции?)

Есть ли способ обнаружить его, не вызывая сбоя компиляции? (Или обходной способ, который по-прежнему использует «множественный улов наследования»)?

+1

Я не думаю, что вы можете сделать эту работу с множественным наследованием. Проблема состоит в том, что это не декларация 'dup_helper ', которая вызывает ошибку, но ее определение (если я не ошибаюсь). – Caninonos

+0

@ Caninonos В теории, Vittorio был инстанцированием 'T' в' decltype (T {}) ', затем получая тип. Витторио надеялся, что 'T {}' будет генерировать ошибку, потому что фактический оператор 'T {}' является ошибкой, даже если тип результата не является. – Yakk

+0

@Yakk, но 'T {}' требует, чтобы компилятор определял 'T', и ошибка находится внутри его определения. Это похоже на попытку иметь ошибку в теле шаблонизированной функции (вместо ее возвращаемого типа возвращаемого типа/noexcept specifier/default template аргумент) и пытаться «поймать» его с помощью sfinae, я не думаю, что это возможно (Я не знаю точно, что говорит стандарт, но все же). (также, чтобы опубликовать «решение» с множественным наследованием, есть [это] (http://ideone.com/oT20iI), он абсолютно ненадежный, хотя, поскольку он полагается на EBO и размер пустого класса, t использовать его) – Caninonos

ответ

6

Как отмечалось @Canoninos, проблема заключается в том, что:

это не декларация dup_helper<T, T>, которая вызывает ошибку, но его определение [...].

Или в Standardese, ошибка происходит за пределами "непосредственного контекста" ([temp.deduct]) замещения:

8 - [...] только недействительные типы и выражения в непосредственном контексте типа функции и его типы параметров шаблона могут привести к потере дедукции. [Примечание: Оценка замещенных типов и выражений может привести к побочным эффектам, таким как экземпляра специализации шаблона класса и/или Специализации шаблона функции, генерация неявно определенных функций, и т.д. Такие побочные эффекты не в «непосредственном контексте» и может привести к плохой форме программы.- конец примечание]

Здесь происходит ошибка в то время как инстанцированииdup_helper<float, float> так не в «непосредственном контексте».

Один многократный трюк наследования, что очень близко к вашему включает в себя добавление дополнительного слоя наследования, путь индексации нескольких баз:

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

Это дает нам вспомогательный класс с действительным определением и которые могут быть созданы, но не отбрасывать ее конечные базовые классы, из-за неопределенности:

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

Example.

+0

There doesn ' t, похоже, являются хорошими «непосредственными контекстными» праймерами в Интернете (вне копий стандарта). У вас есть расшифровка стандарта, который объясняет, почему это «вне непосредственного контекста»? – Yakk

+1

@ Яко мое правило состоит в том, что если вы можете указать на операцию уровня языка, происходящую внутри выражения SFINAE, операнды которого хорошо сформированы, но тем не менее недействительны, тогда вы в порядке; если недействительная операция на уровне языка находится вне выражения SFINAE, это не сработает. – ecatmur

+0

'std :: void_t > (inherit ...> {})) ...) >>' Что такое 'void (...)' делать там, завершая целая вещь в сигнатуру функции, если void_t является переменным в любом случае? – brunocodutra

0

Это мое решение, использующее метапрограммирование и идиому типа. Я использую этот код как часть моей библиотеки, реализующей отражение для C++. Я думаю, что нет необходимости в void_t или наследовании для решения этой задачи.

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

Смежные вопросы