C ++ Шаблонная специализация и создание подклассов - PullRequest
5 голосов
/ 13 мая 2019

Мне было интересно, возможно ли, чтобы специализация шаблона принимала класс и его подклассы. Вот так:

class A {};

class B : public A {};

template <typename T>
void foo(const T& t) {
  printf("T");
}

template <>
void foo(const A& t) {
  printf("A");
}

int main(int argc, char** argv) {
  B b;
  foo(b);

  return 0;
}

В настоящее время выводится 'T', потому что b не имеет собственной специализации шаблона, поэтому по умолчанию он печатает 'T'. Мне было интересно, возможно ли для B использовать специализацию шаблона A, поскольку B является подклассом A. Или это просто не вещь?

Примечание: из-за некоторых требований я не могу использовать копирование / перемещение.

Примечание: я также предпочел бы, если бы мне не нужно было менять A или B, но давайте сначала посмотрим, что возможно.

Ответы [ 4 ]

4 голосов
/ 13 мая 2019

Проблема в том, что основной шаблон является точным соответствием, когда T выводится как B;это лучше, чем специализация.

Вместо этого вы можете использовать перегрузку шаблонов;с SFINAE .

template <typename T>
std::enable_if_t<!std::is_base_of_v<A, T>> foo(const T& t) {
  printf("T");
}

template <typename T>
std::enable_if_t<std::is_base_of_v<A, T>> foo(const T& t) {
  printf("A");
}

LIVE

0 голосов
/ 13 мая 2019

Как насчет этого:

foo(static_cast<A&>(b));
0 голосов
/ 13 мая 2019

Это определенно возможно и не нужно
-копию / ход,
-изменения в классах,
- изменение в теле функций (включая, помимо прочего, вызов другой статической функции шаблона класса ),
- изменить тип / значения возврата функции / с,
-или даже используя константное выражение в качестве типа возврата / s функции / s!

Решение

Просто необходимо иметь общую функцию в качестве шаблона функции, которая использует SFINAE метод в форме аргумента типа / s шаблона по умолчанию для дополнительного параметра (ов) типа шаблона, чтобы избежать особых случаев и иметь специализацию как нормальная функция / с:

template <typename T, typename = std::enable_if_t<!std::is_base_of_v<A, T> > >
void foo(const T& t) {
    printf("T");
}
void foo(const A& t) {
    printf("A");
}


Объяснение:

Для общих случаев аргумент типа шаблона по умолчанию std::enable_if_t<!std::is_base_of_v<A, T> > > может быть выведен из первого аргумента типа шаблона T. Поскольку он существует и он хорошо определен, будет вызван шаблон функции.

Когда функция вызывается с объектом типа, основанного на классе A, поскольку std::enable_if_t<!std::is_base_of_v<A, T> > > не определен, аргумент типа шаблона по умолчанию не существует, следовательно, параметр типа шаблона не может быть выведен. Таким образом, компилятор будет искать другие функции с тем же именем и схожим типом параметра, поэтому обычная функция void foo(const A& t) { printf("A");} будет называться, и не будет никакой двусмысленности.

Примечания по использованию

Для новой специализации просто добавьте еще один похожий псевдокласс в (один) шаблон функции и напишите функцию (не шаблон) для новой специализации.

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

template<typename T, typename P>
using exclude =  std::enable_if_t<!std::is_base_of_v<P, T> >;
template <typename T, typename = exclude<T,A> >
void foo(const T& t) {
    printf("T");
}

Также, поскольку до C ++ 17 определенные функции не были включены, для более низких версий C ++ можно написать шаблон как:

template <typename T, typename  = typename std::enable_if<!std::is_base_of<A, T>::value>::type>

Я добавляю, если кто-то решит использовать решение @songyuanyao, которое использует постоянное выражение в качестве типа возврата функций, если тип возвращаемых функций не void, например return_type, Решение становится как:

template <typename T>
std::enable_if_t<!std::is_base_of_v<A, T>, return_type> foo(const T& t) {
  printf("T");
  return_type return_value;
  return return_value;
}

template <typename T>
std::enable_if_t<std::is_base_of_v<A, T>, return_type> foo(const T& t) {
  printf("A");
  return_type return_value;
  return return_value;
}

Дальнейший пример

Наконец, для лучшего понимания SFINAE Можно рассмотреть не совсем корректное / не всеохватывающее альтернативное решение, которое не требует какой-либо библиотеки:

template<bool>
struct ifnot;
template<>
struct ifnot<false> {
    enum {v};
};
template<typename T, typename P>
struct test {
    static T value_of_T();
    static char check(...);
    static int check(P);
    enum {v = sizeof(check(value_of_T())) - 1};
};

template <typename T, bool = ifnot<test<T, A>::v>::v>
void foo(const T& t) {
    printf("T");
}
void foo(const A& t) {
    printf("A");
}

Хотя это решение также работает для этого конкретного примера, обратите внимание, что это решение не всегда корректно. Потому что он проверяет только преобразование T в A (что само по себе не является полным и проблематичным), а не наследование. Специально для такого рода функций, которые должны вызываться с объектами схожих типов, велика вероятность того, что многие из этих типов будут преобразованы друг в друга!

Я думаю, что правильный путь для теста наследования включает в себя тесты не взаимного преобразования и определение, если ни один из типов не равен void* Все рассмотрено Гораздо лучше использовать std :: is_base_of или std :: is_base_of_v. Однако struct ifnot в порядке, и можно даже обменять std::enable_if на него с соответствующими изменениями в их использовании.

Удачи!

0 голосов
/ 13 мая 2019

Там может быть более чистый способ сделать это. Но если вы измените фактическую реализацию foo на объект SFINAE functionoid , такой как std :: hash, вы можете сохранить перегрузку по умолчанию, не загрязняя ее всеми возможными условиями перегрузки. (Кредит Блог Артура О'Двайера ).

class A {};

class B : public A {};

template <typename T, typename Enable = void>
struct FooImpl {
    static void foo(const T& a) {
        printf("B");
    }
};

template <typename T>
struct FooImpl<T, std::enable_if_t<std::is_base_of_v<A, T>>> {
static void foo(const T& a) {
    printf("A");
}
};

template <typename T>
void foo(const T& t) {
    FooImpl<T>::foo(t);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...