Хорошая практика в отношении специализации и наследования шаблонов - PullRequest
23 голосов
/ 28 октября 2011

Шаблон специализации не учитывает иерархию наследования.Например, если я специализирую шаблон для Base и создаю его экземпляр с помощью Derived, специализация не будет выбрана (см. Код (1) ниже).

Это может быть серьезным препятствием, посколькуиногда приводят к нарушению принципа подстановки Лискова.Например, работая над этим вопросом , я заметил, что не могу использовать алгоритмы Boost.Range с std::sub_match, а с std::pair.Поскольку sub_match публично наследует от pair, здравый смысл подсказывает, что я мог бы заменить sub_match везде, где используется pair, но это не удалось из-за классов признаков, использующих специализацию шаблонов.преодолеть эту проблему, используя частичную специализацию шаблонов вместе с enable_if и is_base_of (см. code (2)).Должен ли я всегда отдавать предпочтение этому решению, а не полной специализации, особенно при написании кода библиотеки?Есть ли какие-либо недостатки этого подхода, который я наблюдал?Это практика, которую вы используете или видели часто?


Примеры кодов

(1)
#include <iostream>

struct Base {};
struct Derived : public Base {};

template < typename T >
struct Foo
{
    static void f() { std::cout << "Default" << std::endl; }
};

template <>
struct Foo< Base >
{
    static void f() { std::cout << "Base" << std::endl; }
};

int main()
{
    Foo<Derived>::f(); // prints "Default"
}

(2)
#include <type_traits>
#include <iostream>

struct Base {};
struct Derived : public Base {};

template <typename T, typename Enable = void>
struct Foo
{
    static void f() { std::cout << "Default" << std::endl; }
};

template <typename T>
struct Foo<
    T, typename 
    std::enable_if< std::is_base_of< Base, T >::value >::type
>
{
    static void f() { std::cout << "Base" << std::endl; }
};

int main()
{
    Foo<Derived>::f(); // prints "Base"
}

1 Ответ

13 голосов
/ 28 октября 2011

enable_if более гибкий

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

например. Могут быть случаи, когда производный класс имеет значение Liskov-Subsitutable для Base, , но вы [не можете предположить / не хотите применять] те же черты / специализации, чтобы быть действительными (например, потому что Base - это класс POD, тогда как производное и не POD поведение или что-то подобное, что полностью ортогонально по отношению к составу класса).

enable_if дает вам возможность точно определить условия.

Гибридный подход

Вы также можете достичь некоторого среднего уровня, реализовав класс черт, который выводит некоторые черты, специфичные для приложения, из черт общего назначения. «Пользовательские» черты могут использовать методы enable_if и метапрограммирования, чтобы применять черты так полиморфно, как вы пожелаете. Таким образом, ваши фактические реализации не должны повторять какой-то сложный танец enable_if / dispatch, а вместо этого могут просто потреблять класс custom-traits (который скрывает сложность).

Я думаю, что некоторые (многие?) Библиотеки Boost используют гибридный подход (я видел его в некоторой степени, когда он соединяет, например, fusion / mpl, я думаю, что также различные черты итератора в Spirit).

Мне лично нравится такой подход, потому что он может эффективно изолировать «сантехнику» от основной деятельности библиотеки, делая обслуживание и документацию (!) Намного проще.

...