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

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

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

}

8 ответов

Решение

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

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

}

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

С 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.

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

Особенно:

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>();
}

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

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

Решение David Rodríguez - dribeas можно переписать следующим образом:

#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,

Вам не нужны концепции, но вы можете использовать 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
}

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

Начиная с C++20, вы можете ограничивать аргументы шаблона. Одним из способов сделать это является оговорка. Вы можете использовать его следующим образом в вашем случае (т.е. убедиться, что это подкласс определенного классаBaseclass):

      template <class T> void function()
   requires( std::is_base_of_v<Baseclass,T> )
{
   T *object = new T();
}

В качестве альтернативы, для часто используемых ограничений вы можете использовать именованные концепции, которые позволяют использовать более краткий синтаксис (кредит @sklott):

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

Использование ограничений лучше, чем использование , потому что они позволят вам иметь несколько реализацийfunctionс разными ограничениями. Например, у вас может быть другая реализация, когдаTпроисходит из другого класса. При желании вы также можете иметь реализацию по умолчанию, которая вообще не определяет никаких ограничений, чтобы компилятор выбрал ее, когда ни одна из реализаций с ограничениями не подходит. Ни один из них не был бы возможен при использованииstatic_assert.

PS. Предложение отлично подходит для специальных ограничений, а именованные концепции отлично подходят для часто используемых ограничений (вы также можете создавать свои собственные именованные концепции, используяconceptключевое слово). Подробнее об ограничениях и альтернативных способах их применения можно прочитать здесь, оstd::is_base_of_v вот и оstd::derived_from здесь . Для более продвинутых вариантов использования также обязательно прочитайте о выражениях require, которые не следует путать с предложением require, даже если они имеют одно и то же значение.requiresключевое слово.

Вы можете использовать Boost Concept CheckBOOST_CONCEPT_REQUIRES:

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

template <class T>
BOOST_CONCEPT_REQUIRES(
    ((boost::Convertible<T, BaseClass>)),
(void)) function()
{
    //...
}

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

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

Другие вопросы по тегам