Специализация шаблонов и производные классы в С++ - программирование
Подтвердить что ты не робот

Специализация шаблонов и производные классы в С++

У меня есть этот простой код:

class A{};
class B : public A{};
class C : public B{};

class Test
{
    public:
        template<typename T>
        void f(T&){printf("template\n");}
        void f(A&){printf("specialization\n");}
};

int main()
{
    A a;
    B b;
    C c;

    Test test;
    test.f(a);
    test.f(b);
    test.f(c);
}

Когда я запускаю его (VS2010), у меня есть этот вывод:

specialization
template
template

Можно ли использовать вызовы с классами A-derived для использования специализации?

4b9b3361

Ответ 1

Да, это возможно, но вам нужно немного изменить свой код.

Прежде всего, чтобы быть техническим, вторая функция f() - это not специализация функции шаблона, но перегрузка. При разрешении перегрузки версия шаблона выбирается для всех аргументов, тип которых не является A, потому что это идеальное совпадение: T выводится равным типу аргумента, поэтому при вызове f(b) для экземпляр, после вывода типа, компилятор должен будет выбрать между двумя следующими перегрузками:

void f(B&){printf("template\n");}
void f(A&){printf("specialization\n");}

Конечно, первое - лучшее совпадение.

Теперь, если вы хотите, чтобы вторая версия была выбрана при вызове функции с аргументом, который является подклассом A, вы должны использовать некоторую технику SFINAE, чтобы предотвратить создание шаблона функции, когда тип T выводится как подкласс A.

Вы можете использовать std::enable_if в сочетании с чертами типа std::is_base_of, чтобы достичь этого.

// This will get instantiated only for those T which are not derived from A
template<typename T,
    typename enable_if<
        !is_base_of<A, T>::value
        >::type* = nullptr
    >
void f(T&) { cout << "template" << endl; }

Вот как вы могли бы использовать его в полной программе:

#include <type_traits>
#include <iostream>

using namespace std;

class A{};
class B : public A{};
class C : public B{};
class D {};

class Test
{
    public:

        template<typename T,
            typename enable_if<!is_base_of<A, T>::value>::type* = nullptr
            >
        void f(T&) { cout << ("template\n"); }

        void f(A&){ cout << ("non-template\n");}

};

int main()
{
    A a;
    B b;
    C c;
    D d;
    float f;

    Test test;
    test.f(a); // Will print "non-template"
    test.f(b); // Will print "non-template"
    test.f(c); // Will print "non-template"
    test.f(d); // Will print "template"
    test.f(f); // Will print "template"
}

EDIT:

Если вы работаете с компилятором, который не полностью совместим с С++ 11 (и поэтому не поддерживает аргументы шаблона по умолчанию в шаблонах функций), вы можете изменить определение перегрузки шаблона f() как следующим образом:

template<typename T>
typename enable_if<!is_base_of<A, T>::value, void>::type 
f(T&) { cout << ("template\n"); }

Поведение программы будет идентичным. Обратите внимание: если тип возврата f() равен void, вы можете опустить второй аргумент в шаблон класса enable_if.