Подтвердить что ты не робот

Надлежащая практика в отношении специализированной специализации и наследования

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

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

Мы можем преодолеть эту проблему, используя специализированную специализацию по шаблону вместе с enable_if и is_base_of (см. код (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"
}
4b9b3361

Ответ 1

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

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

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

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

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

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

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

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