Обнаружение наследования того же класса с помощью SFINAE

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

Проблема в том, что в следующем коде происходит сбой компиляции, хотя я ожидаю, что SFINAE будет работать.

Изменить. Вопрос не в том, "как написать эту метафункцию", а в том, "как мне поймать эту ошибку двойного наследования и вывести false_type, когда это произойдет". AFAIK, это возможно только с SFINAE.


template <typename T>
struct dummy {};

// error: duplicate base type ‘dummy<int>’ invalid
template <typename T, typename U>
struct fail : dummy<T>, dummy<U> {};

template <typename T>
true_type test(fail<T, T> a = fail<T, T>());

false_type test(...);

int main() {
    cout << decltype(test<int>())::value << endl;
}

Текущая версия здесь.


Изменить. Раньше я пытался сделать это с ошибкой специализации, но это не сработало с той же ошибкой компиляции.

template <typename T>
struct dummy {};

template <typename T, typename U>
struct fail : dummy<T>, dummy<U>, true_type {};

template <typename T, typename U = void>
struct test : false_type {};

template <typename T>
struct test<T, typename enable_if<fail<T, T>::value, void>::type> : true_type {};

Текущая версия здесь.


person polkovnikov.ph    schedule 05.06.2014    source источник
comment
Если вы хотите использовать только T и U, вы можете использовать std::is_same<T, U>::value. если вам нужен вариативный шаблон, это сложнее.   -  person Jarod42    schedule 05.06.2014
comment
@ Jarod42 Я отредактировал вопрос, чтобы он был более конкретным. Извиняюсь.   -  person polkovnikov.ph    schedule 05.06.2014


Ответы (5)


Вы не можете поймать двойное наследование с помощью SFINAE, потому что это не одна из перечисленных причин, по которым дедукция не работает в соответствии с 14.8.2p8 [temp.deduct]; в равной степени это происходит из-за того, что ошибка возникает вне "непосредственного контекста" вывода шаблона, так как это ошибка создания экземпляра вашего struct fail.

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

C<> A<int>
|  /
C<int> A<char>
|     /
C<char, int> A<int>
|           /
C<int, char, int>

Теперь преобразование из C<int, char, int> в A<int> будет неоднозначным, а поскольку неоднозначное преобразование отмечено в списке 14.8.2p8, мы можем использовать SFINAE для его обнаружения:

#include <type_traits>

template<class> struct A {};
template<class... Ts> struct C;
template<> struct C<> {};
template<class T, class... Ts> struct C<T, Ts...>: A<T>, C<Ts...> {};
template<class... Ts> void f(A<Ts>...);
template<class... Ts> std::false_type g(...);
template<class... Ts> decltype(f((A<Ts>(), C<Ts...>())...), std::true_type()) g(int);
template<class... Ts> using distinct = decltype(g<Ts...>(0));

static_assert(distinct<int, char, float>::value, "!!");
static_assert(!distinct<int, char, int>::value, "!!");
person ecatmur    schedule 05.06.2014
comment
... и отправил отчет об ошибке команде Visual Studio, так как этот код, очевидно, там не работает. - person polkovnikov.ph; 06.06.2014
comment
Для нерекурсивного решения (вариативное множественное наследование) см. stackoverflow.com/a/32017587/567292. - person ecatmur; 14.08.2015

ОШИБКА

prog.cpp: In instantiation of ‘struct fail<int, int>’: prog.cpp:23:29: required from here prog.cpp:9:8: error: duplicate base type ‘dummy<int>’ invalid struct fail : dummy<T>, dummy<U> {};

Как указано в приведенной выше диагностике, вы не можете явно наследовать от одной и той же базы более одного раза в списке спецификаторов базы, а поскольку T и U могут принадлежать одному и тому же тип.. БУМ.


ПОДОЖДИТЕ, ПОДДЕРЖИТЕ; ЧТО О SFINAE?

SFINAE проверяется только в непосредственном контексте самого шаблона, ошибки, возникающие вне объявления, не подходят для запуска SFINAE.

14.8.2p8 Вывод аргумента шаблона [temp.deduct]

Если подстановка приводит к недопустимому типу или выражению, вывод типа завершается ошибкой. Недопустимый тип или выражение — это тип, который будет иметь неправильный формат и потребует диагностики, если будет записан с использованием подставленных аргументов.

Только недопустимые типы и выражения в непосредственном контексте типа функции и ее типов параметров шаблона могут привести к ошибке вывода.

Плохо сформированное наследование не происходит в непосредственном контексте, и приложение неправильно сформировано.

Чтобы ответить на ваш вопрос явно: поскольку наследование никогда не происходит в объявлении определенной функции, само неправильно сформированное наследование не может быть обнаружено с помощью SFINAE.

Конечно, вы можете попросить компилятор сгенерировать класс, который использует наследование, создав его экземпляр в объявлении функции, но фактическое (неправильное) наследование не находится в непосредственном контексте< /эм>.

person Filip Roséen - refp    schedule 05.06.2014
comment
@polkovnikov.ph, что не было указано в вашем вопросе, основная цель этого поста - ответить Почему?, реализация - бонус. Позвольте мне написать для вас оптимизированную версию, просто чтобы показать, что это тоже возможно. Но как сказано; Почему? был дан ответ. - person Filip Roséen - refp; 05.06.2014
comment
@polkovnikov.ph Я четко ответил на ваш вопрос: недопустимый базовый-спецификатор-список не может быть создан и перехвачен с помощью SFINAE. Вам нужно будет сначала проверить, возможно ли такое наследование, и если да; либо создавать экземпляр, либо нет. - person Filip Roséen - refp; 05.06.2014
comment
О, теперь я вижу. Глупый я. - person polkovnikov.ph; 05.06.2014

Как я понимаю, вы хотите, чтобы трейты проверяли, все ли типы разные,
может помочь следующее:

#include <type_traits>

template <typename T, typename ...Ts> struct is_in;

template <typename T> struct is_in<T> : std::false_type {};
template <typename T1, typename T2, typename ... Ts>
struct is_in<T1, T2, Ts...> : std::conditional<std::is_same<T1, T2>::value, std::true_type, is_in<T1, Ts...>>::type {};


template <typename ... Ts> struct are_all_different;

template <> struct are_all_different<> : std::true_type {};
template <typename T> struct are_all_different<T> : std::true_type {};

template <typename T1, typename T2, typename ... Ts> struct are_all_different<T1, T2, Ts...> :
    std::conditional<is_in<T1, T2, Ts...>::value, std::false_type, are_all_different<T2, Ts...>>::type {};


static_assert(are_all_different<char, int, float, long, short>::value, "type should be all different");
static_assert(!are_all_different<char, int, float, char, short>::value, "type should not all different");
person Jarod42    schedule 05.06.2014
comment
@Filip Roséen: Человек, ответы могут быть либо какими-то объяснениями, которые ничем не помогают (см. Ваш пост), либо решениями, подобными этому (которые также могут включать подсказки или лучшие практики, о которых ОП не подумал). Мне больше нравятся решения. - person davidhigh; 05.06.2014
comment
@davidhigh: предоставление обходных путей без объяснения того, почему конкретный предпринятый подход не сработал, полезно в краткосрочной перспективе, но не в долгосрочной перспективе. Дай человеку рыбу... - person Matthieu M.; 05.06.2014
comment
@FilipRoséen-refp: При этом, и хотя вы можете голосовать по своему собственному желанию, имейте в виду, что ряд людей считают отрицательные голоса агрессивными (в лучшую или худшую сторону) и, следовательно, отрицательные голоса полезны (если они неполные). ) ответы будут восприняты вами как агрессивные. Я бы посоветовал комментировать то, что вы считаете неполными ответами, без отрицательного голосования; Вам решать следовать ему или нет :) - person Matthieu M.; 05.06.2014
comment
Я согласен с тем, что я не (полностью) отвечаю на вопрос OP, и объяснение непосредственного контекста отсутствует (но я считаю грубым голосовать за это). - person Jarod42; 05.06.2014
comment
Хорошо, я также согласен со всеми вами: иметь в своем посте и конкретный ответ, и возможные улучшения, безусловно, лучший вариант. Так что давайте дружить :-D - person davidhigh; 05.06.2014
comment
@davidhigh Я знаю о реализации рекурсивных метафункций, но для ускорения метавычислений были добавлены вариативные шаблоны, и я хотел бы оставить рекурсию для C++03. - person polkovnikov.ph; 05.06.2014
comment
@polkovnikov.ph Я не совсем понимаю, что вы имеете в виду, поскольку именно вы использовали вариативные шаблоны - эксклюзивную функцию C++11 - в своем первоначальном вопросе ... поэтому, пожалуйста, объясните. - person davidhigh; 05.06.2014
comment
@davidhigh Неважно, @FilipRoséen только что объяснил, почему решение O(1) с амортизированными экземплярами невозможно. - person polkovnikov.ph; 05.06.2014
comment
@FilipRoséen-refp: удаление всех ваших комментариев делает весь список комментариев бессвязным, если не почти бесполезным. Это действительно необходимо? - person davidhigh; 06.06.2014

Как насчет простого std::is_same<>? Насколько я вижу, он напрямую моделирует желаемое поведение вашего класса.

Итак, попробуйте что-то вроде этого:

template<typename T, typename U>
fail
{
    fail(const T &t, const U &u)
    {
         static_assert(std::is_same<T,U>::value,"T and U must have a distinct type");
    }
};

Или, что еще лучше, напрямую используйте std::is_same<T,U> в своем коде.

EDIT: Вот решение, вдохновленное Jarod42, но использующее только один класс (чтобы было понятнее: это ответ на вопрос как написать шаблон класса с переменным числом переменных, который определяет, все ли данные типы различны, что казалось желаемой целью в одной из ранних версий исходного вопроса):

#include <type_traits>

template <typename ...Ts> struct are_all_different {};
template <> struct are_all_different<> {static const bool value=true;};
template <typename T> struct are_all_different<T> {static const bool value=true;};

template <typename T1, typename T2>
struct are_all_different<T1, T2>
{
    static const bool value = !std::is_same<T1, T2>::value;
};

template <typename T1, typename T2, typename ...Ts>
struct are_all_different<T1,T2,Ts...>
{
    static const bool value = are_all_different<T1, T2>::value
                           && are_all_different<T1, Ts...>::value
                           && are_all_different<T2, Ts...>::value;
};

static_assert(are_all_different<char, int, float, long, short>::value, "type should be all different");
static_assert(!are_all_different<char, int, float, char, short>::value, "type should not all different");

В конце концов, для n вариативных аргументов шаблона это должно проверять равенство всех n*(n+1)/2 комбинаций.

person davidhigh    schedule 05.06.2014

Я не очень понимаю, чего вы пытаетесь добиться, но я могу помочь вам с ошибкой

template <typename T, typename U>
struct fail : dummy<T>, dummy<U> {};

template <typename T>
struct fail<T, T> : dummy<T> {};

Ошибка заключается в том, что когда вы создаете экземпляр fail с T и U одним и тем же, вы в основном наследуете от одного и того же класса дважды, что является незаконным, поэтому вам нужно создать специализацию, чтобы позаботиться об этом случае.

person bolov    schedule 05.06.2014
comment
ОП хочет черты типа are_all_different<Ts...> - person Jarod42; 05.06.2014
comment
@ Jarod42 Я понял, просто не понимаю, как все это ему поможет. Я бы пошел другим путем, чтобы добиться этого. Но если он может сделать эту работу, все остальное не имеет значения. - person bolov; 05.06.2014
comment
Разве я не говорил, что знаю источник ошибки? Не похоже ли, что ошибка была добавлена ​​туда намеренно? - person polkovnikov.ph; 05.06.2014
comment
@polkovnikov.ph где ты сказал, что знаешь ошибку? Мне не кажется, что ошибка была добавлена ​​туда намеренно. Может быть, я не понимаю вашего поста, в таком случае извините. Цитата из вашего вопроса: The problem is that compilation fails in the following code, while I would expect SFINAE to work. - person bolov; 05.06.2014
comment
@bolov Я исправил вопрос, чтобы он был более конкретным. Извиняюсь. - person polkovnikov.ph; 05.06.2014