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

80

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

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

}
phant0m
источник
3
Чего вы этим пытаетесь достичь?
чт,
2
Я просто хочу убедиться, что T на самом деле является экземпляром подкласса или самого класса. Код внутри функции, которую я предоставил, практически не имеет значения.
phant0m 04
6
напротив, это очень актуально. Он определяет, стоит ли выполнять этот тест - хорошая идея или нет. Во многих (всех?) Случаях нет абсолютно никакой необходимости устанавливать такие ограничения самостоятельно, а лучше позволить компилятору сделать это при создании экземпляра. Например, для принятого ответа было бы хорошо поставить проверку, Tполучено ли из Baseclass. На данный момент эта проверка является неявной и не видна для разрешения перегрузки. Но если нигде такое неявное ограничение не применяется, похоже, нет причин для искусственного ограничения.
Йоханнес Шауб - лит
1
Да, я согласен. Однако я просто хотел знать, есть ли способ добиться этого или нет :) Но, конечно, у вас есть очень веский аргумент, и спасибо за понимание.
phant0m 04

Ответы:

53

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

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

}

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

sepp2k
источник
ах да, это хорошая идея. благодаря! Я так понимаю, что нет способа определить это в определении шаблона?
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.

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

Вы можете использовать подталкивание Concept Check «ы BOOST_CONCEPT_REQUIRES:

#include <boost/concept_check.hpp>
#include <boost/concept/requires.hpp>

template <class T>
BOOST_CONCEPT_REQUIRES(
    ((boost::Convertible<T, BaseClass>)),
(void)) function()
{
    //...
}
Даниэль Треббиен
источник
0

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

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

ДанДан
источник
3
Это не гарантирует, что T это a, BaseClass потому что объявленные члены BaseClassмогут повторяться в объявлении T.
Даниэль Треббиен,