Обмежте параметр шаблону C ++ лише підкласом


80

Як я можу змусити параметр шаблону Tбути підкласом певного класу Baseclass? Щось на зразок цього:

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

}

3
Що ви намагаєтесь досягти цим?
sth

2
Я просто хочу переконатися, що T насправді є екземпляром підкласу або самого класу. Код всередині функції, яку я надав, є майже неактуальним.
phant0m

6
навпаки, це дуже актуально. Це визначає, чи є гарною ідеєю чи ні вкладати роботу в цей тест. У багатьох (усіх?) Випадках абсолютно немає необхідності застосовувати такі обмеження самостійно, а, швидше, нехай компілятор робить це під час створення екземпляра. Наприклад, для прийнятої відповіді було б непогано поставити перевірку, чи Tпоходить від Baseclass. На сьогоднішній день ця перевірка є неявною і не відображається для дозволу на перевантаження. Але якщо ніде не робиться такого неявного обмеження, здається, немає причин для штучного обмеження.
Йоханнес Шауб - Litb

1
Так, я згоден. Однак я просто хотів дізнатись, чи є спосіб досягти цього чи ні :) Але, звичайно, у вас є дуже вагомий пункт і спасибі за розуміння.
phant0m

Відповіді:


53

У цьому випадку ви можете зробити:

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

}

Це не буде скомпільовано, якщо T не є підкласом базового класу (або T є базовим класом).


а так, це гарна ідея. Дякую! Я вважаю, тоді немає способу визначити це у визначенні шаблону?
phant0m

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

1
ай ік. Велике спасибі за роз'яснення!
phant0m

8
Це виконує конструктор T () і вимагає існування конструктора T (). Дивіться мою відповідь, щоб уникнути цих вимог.
Дуглас Лідер,

3
Приємно і зрозуміло, але це проблема, якщо Т - "важкий" клас.
3Dave

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
Я думаю, що це найбільш читабельна відповідь, яка дозволяє уникнути зайвого коду під час виконання.
Кайл

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
Для мене це найкраща і найцікавіша відповідь. Обов’язково ознайомтеся з поширеними запитаннями про Stroustrup, щоб прочитати більше про всі види обмежень, які ви можете застосувати подібним чином до цього.
Жан-Філіп Пелле

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

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>
Максінус


0

Викликаючи функції всередині вашого шаблону, які існують у базовому класі.

Якщо ви спробуєте створити шаблон для вашого типу, який не має доступу до цієї функції, ви отримаєте помилку під час компіляції.


3
Це не гарантує, що T це BaseClass тому, що заявлені члени в BaseClassможуть бути повторені в декларації T.
Даніель Треббьєн,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.