Ограничить параметр шаблона C ++ подклассом


80

Как я могу заставить параметр шаблона Tбыть подклассом определенного класса Baseclass? Что-то вроде этого:

template <class T : Baseclass> void function(){
    T *object = new T();

}

3
Чего вы этим пытаетесь достичь?
чт,

2
Я просто хочу убедиться, что T на самом деле является экземпляром подкласса или самого класса. Код внутри функции, которую я предоставил, практически не имеет значения.
phant0m 04

6
напротив, это очень актуально. Он определяет, стоит ли выполнять этот тест - хорошая идея или нет. Во многих (всех?) Случаях нет абсолютно никакой необходимости устанавливать такие ограничения самостоятельно, а лучше позволить компилятору сделать это при создании экземпляра. Например, для принятого ответа было бы хорошо поставить проверку, Tполучено ли из Baseclass. На данный момент эта проверка является неявной и не видна для разрешения перегрузки. Но если нигде такое неявное ограничение не применяется, похоже, нет причин для искусственного ограничения.
Йоханнес Шауб - лит

1
Да, я согласен. Однако я просто хотел знать, есть ли способ добиться этого или нет :) Но, конечно, у вас есть очень веский аргумент, и спасибо за понимание.
phant0m 04

Ответы:


53

В этом случае вы можете:

template <class T> void function(){
    Baseclass *object = new T();

}

Это не будет компилироваться, если T не является подклассом Baseclass (или T является базовым классом).


ах да, это хорошая идея. благодаря! Я так понимаю, что нет способа определить это в определении шаблона?
phant0m 04

2
@ phant0m: Верно. Вы не можете явно ограничить параметры шаблона (за исключением использования концепций, которые были рассмотрены для c ++ 0x, но затем отброшены). Все ограничения неявно возникают в результате операций, которые вы выполняете над ним (или, другими словами, единственным ограничением является «Тип должен поддерживать все операции, которые выполняются над ним»).
sepp2k 04

1
ай ic. Большое спасибо за разъяснения!
phant0m 04

8
Это выполняет конструктор T () и требует наличия конструктора T (). См. Мой ответ, как избежать этих требований.
Дуглас Лидер 05

3
Красиво и понятно, но это проблема, если Т - «тяжелый» класс.
3ave

84

С компилятором, совместимым с C ++ 11, вы можете сделать что-то вроде этого:

template<class Derived> class MyClass {

    MyClass() {
        // Compile-time sanity check
        static_assert(std::is_base_of<BaseClass, Derived>::value, "Derived not derived from BaseClass");

        // Do other construction related stuff...
        ...
   }
}

Я тестировал это с помощью компилятора gcc 4.8.1 в среде CYGWIN, поэтому он должен работать и в средах * nix.


Для меня это работает так же: template<class TEntity> class BaseBiz { static_assert(std::is_base_of<BaseEntity, TEntity>::value, "TEntity not derived from BaseEntity");...
Маттиас Дитер Валлнефер

1
Я думаю, что это наиболее читаемый ответ, позволяющий избежать лишнего кода во время выполнения.
Kyle

50

Чтобы выполнить менее бесполезный код во время выполнения, вы можете посмотреть: http://www.stroustrup.com/bs_faq2.html#constraints, который предоставляет некоторые классы, которые эффективно выполняют тест времени компиляции и создают более приятные сообщения об ошибках.

В частности:

template<class T, class B> struct Derived_from {
        static void constraints(T* p) { B* pb = p; }
        Derived_from() { void(*p)(T*) = constraints; }
};

template<class T> void function() {
    Derived_from<T,Baseclass>();
}

2
Для меня это лучший и самый интересный ответ. Обязательно ознакомьтесь с FAQ Страуструпа, чтобы узнать больше обо всех видах ограничений, которые вы можете применить аналогично этому.
Жан-Филипп Пелле

1
Действительно, это чертовски ответ! Благодарю. Упомянутый сайт перемещен сюда: stroustrup.com/bs_faq2.html#constraints
Ян Корор

Это отличный ответ. Есть ли хорошие способы избежать предупреждений unused variable 'p'и unused variable 'pb'?
Филип С.

@FilipS. добавить (void)pb;после B* pb = p;.
bit2shift

11

Вам не нужны концепции, но вы можете использовать SFINAE:

template <typename T>
boost::enable_if< boost::is_base_of<Base,T>::value >::type function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Обратите внимание, что это создаст экземпляр функции только при выполнении условия, но не приведет к существенной ошибке, если условие не будет выполнено.


Что, если вы обернете все функции таким образом? кстати, что он возвращает?
the_drow

enable_ifЗанимает второй тип параметра , который по умолчанию void. Выражение enable_if< true, int >::typeпредставляет тип int. Я не могу понять, в чем заключается ваш первый вопрос, вы можете использовать SFINAE для чего угодно, но я не совсем понимаю, что вы собираетесь делать с этим для всех функций.
Дэвид Родригес - dribeas 04

7

Начиная с C ++ 11, вам не нужны Boost или static_assert. C ++ 11 представляет is_base_of и enable_if. C ++ 14 вводит удобный тип enable_if_t, но если вы застряли на C ++ 11, вы можете просто использовать enable_if::typeвместо него.

Альтернатива 1

Решение Давида Родригеса можно переписать следующим образом:

#include <type_traits>

using namespace std;

template <typename T>
enable_if_t<is_base_of<Base, T>::value, void> function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Альтернатива 2

Начиная с C ++ 17, у нас есть is_base_of_v. В дальнейшем решение можно переписать так:

#include <type_traits>

using namespace std;

template <typename T>
enable_if_t<is_base_of_v<Base, T>, void> function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Альтернатива 3

Вы также можете просто ограничить весь шаблон. Вы можете использовать этот метод для определения целых классов. Обратите внимание, как enable_if_tбыл удален второй параметр (ранее он был установлен на void). Его значение по умолчанию - на самом деле void, но это не имеет значения, поскольку мы его не используем.

#include <type_traits>

using namespace std;

template <typename T,
          typename = enable_if_t<is_base_of_v<Base, T>>>
void function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Из документации параметров шаблона мы видим, что typename = enable_if_t...это параметр шаблона с пустым именем. Мы просто используем его, чтобы гарантировать, что определение типа существует. В частности, enable_if_tне будет определен, если Baseне является базой T.

Приведенная выше методика приведена в качестве примера в enable_if.


Было бы неплохо, если бы альтернативу 3 можно было написать следующим образом? template <class T : Base>
Macsinus


0

Вызывая внутри вашего шаблона функции, существующие в базовом классе.

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


3
Это не гарантирует, что T это a, BaseClass потому что объявленные члены BaseClassмогут повторяться в объявлении T.
Даниэль Треббиен,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.