Шаблоны C ++, которые принимают только определенные типы - PullRequest
133 голосов
/ 17 мая 2009

В Java вы можете определить универсальный класс, который принимает только те типы, которые расширяют класс по вашему выбору, например:

public class ObservableList<T extends List> {
  ...
}

Это делается с помощью ключевого слова extends.

Есть ли какой-нибудь простой эквивалент этого ключевого слова в C ++?

Ответы [ 13 ]

98 голосов
/ 09 августа 2014

Как правило, это неоправданно в C ++, как отмечалось в других ответах. В C ++ мы стремимся определять универсальные типы на основе других ограничений, кроме «наследуется от этого класса». Если вы действительно хотели это сделать, это довольно легко сделать в C ++ 11 и <type_traits>:

#include <type_traits>

template<typename T>
class observable_list {
    static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
    // code here..
};

Это нарушает многие концепции, которые люди ожидают в C ++. Лучше использовать такие приемы, как определение собственных черт. Например, возможно, observable_list хочет принять любой тип контейнера, который имеет функции typedefs const_iterator и begin и end, которые возвращают const_iterator. Если вы ограничите это классами, которые наследуют от list, то пользователь, который имеет свой собственный тип, который не наследует от list, но предоставляет эти функции-члены и typedefs, не сможет использовать ваш observable_list.

Существует два решения этой проблемы, одно из которых - не ограничивать что-либо и полагаться на типизацию утки. Большим недостатком этого решения является то, что оно включает в себя огромное количество ошибок, которые могут быть сложными для пользователей. Другое решение состоит в том, чтобы определить признаки, чтобы ограничить тип, обеспеченный, чтобы удовлетворить требованиям интерфейса. Большой минус для этого решения заключается в том, что это требует дополнительной записи, что может показаться раздражающим. Тем не менее, положительным моментом является то, что вы сможете писать свои собственные сообщения об ошибках а-ля static_assert.

Для полноты приведено решение для приведенного выше примера:

#include <type_traits>

template<typename...>
struct void_ {
    using type = void;
};

template<typename... Args>
using Void = typename void_<Args...>::type;

template<typename T, typename = void>
struct has_const_iterator : std::false_type {};

template<typename T>
struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};

struct has_begin_end_impl {
    template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
                         typename End   = decltype(std::declval<const T&>().end())>
    static std::true_type test(int);
    template<typename...>
    static std::false_type test(...);
};

template<typename T>
struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};

template<typename T>
class observable_list {
    static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
    static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
    // code here...
};

В приведенном выше примере показано много концепций, демонстрирующих возможности C ++ 11. Некоторыми поисковыми терминами для любопытных являются шаблоны с переменными значениями, SFINAE, выражение SFINAE и черты типа.

98 голосов
/ 17 мая 2009

Я предлагаю использовать функцию статического подтверждения в Boost совместно с is_base_of из библиотеки признаков типа усиления:

template<typename T>
class ObservableList {
    BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
    ...
};

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

template<typename T> class my_template;     // Declare, but don't define

// int is a valid type
template<> class my_template<int> {
    ...
};

// All pointer types are valid
template<typename T> class my_template<T*> {
    ...
};

// All other types are invalid, and will cause linker error messages.

[Незначительное изменение 6/12/2013: использование объявленного, но не определенного шаблона приведет к компоновщику , а не к компилятору, сообщений об ошибках.]

56 голосов
/ 17 мая 2009

Простое решение, о котором еще никто не упомянул, - просто игнорировать проблему. Если я попытаюсь использовать int в качестве типа шаблона в шаблоне функции, который ожидает контейнерный класс, такой как вектор или список, то я получу ошибку компиляции. Грубо и просто, но это решает проблему. Компилятор попытается использовать указанный вами тип, и если это не удастся, он выдаст ошибку компиляции.

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

Если вам нужны более приятные сообщения об ошибках (или если вы хотите отлавливать случаи, которые не приводят к ошибке компилятора, но все же не имеют смысла), вы можете, в зависимости от того, насколько сложным вы хотите это сделать, используйте либо Boost's статическое утверждение или библиотека Boost concept_check.

С современным компилятором у вас есть встроенный_100 *, который можно использовать вместо него.

13 голосов
/ 17 мая 2009

Насколько я знаю, в настоящее время это невозможно в C ++. Тем не менее, в новый стандарт C ++ 0x планируется добавить функцию, называемую «концепциями», которая обеспечивает нужную вам функциональность. Эта статья Википедии о C ++ Concepts объяснит это более подробно.

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

11 голосов
/ 13 августа 2014

Мы можем использовать std::is_base_of и std::enable_if:
(static_assert может быть удалено, вышеупомянутые классы могут быть реализованы на заказ или использованы из boost , если мы не можем ссылаться на type_traits)

#include <type_traits>
#include <list>

class Base {};
class Derived: public Base {};

#if 0   // wrapper
template <class T> class MyClass /* where T:Base */ {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
    typename std::enable_if<std::is_base_of<Base, T>::value, T>::type inner;
};
#elif 0 // base class
template <class T> class MyClass: /* where T:Base */
    protected std::enable_if<std::is_base_of<Base, T>::value, T>::type {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
};
#elif 1 // list-of
template <class T> class MyClass /* where T:list<Base> */ {
    static_assert(std::is_base_of<Base, typename T::value_type>::value , "T::value_type is not derived from Base");
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type base; 
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type::value_type value_type;

};
#endif

int main() {
#if 0   // wrapper or base-class
    MyClass<Derived> derived;
    MyClass<Base> base;
//  error:
    MyClass<int> wrong;
#elif 1 // list-of
    MyClass<std::list<Derived>> derived;
    MyClass<std::list<Base>> base;
//  error:
    MyClass<std::list<int>> wrong;
#endif
//  all of the static_asserts if not commented out
//  or "error: no type named ‘type’ in ‘struct std::enable_if<false, ...>’ pointing to:
//  1. inner
//  2. MyClass
//  3. base + value_type
}
10 голосов
/ 13 августа 2014

Эквивалент, который принимает только типы T, производные от типа List, выглядит как

template<typename T, 
         typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr>
class ObservableList
{
    // ...
};
7 голосов
/ 10 августа 2014

Я думаю, что все предыдущие ответы потеряли из виду лес за деревьями.

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

Вместо того, чтобы пытаться использовать конструкцию времени компиляции для имитации времени выполнения, давайте посмотрим, что на самом деле делает extends: в соответствии с переполнением стека и Wikipedia используется для обозначения подклассов.

C ++ также поддерживает создание подклассов.

Вы также показывает контейнерный класс, который использует стирание типа в форме универсального и расширяет возможности для проверки типа. В C ++ вы должны самостоятельно выполнить механизм стирания типов, что очень просто: создать указатель на суперкласс.

Давайте обернем его в typedef, чтобы его было проще использовать, а не создавать целый класс, и т. Д .:

typedef std::list<superclass*> subclasses_of_superclass_only_list;

Например:

class Shape { };
class Triangle : public Shape { };

typedef std::list<Shape*> only_shapes_list;
only_shapes_list shapes;

shapes.push_back(new Triangle()); // Works, triangle is kind of shape
shapes.push_back(new int(30)); // Error, int's are not shapes

Теперь, похоже, List - это интерфейс, представляющий собой своего рода коллекцию. Интерфейс в C ++ будет просто абстрактным классом, то есть классом, который реализует только чистые виртуальные методы. Используя этот метод, вы можете легко реализовать свой пример Java в C ++, без каких-либо концепций или шаблонных специализаций. Он также будет работать так же медленно, как универсальные стили в стиле Java, из-за поиска в виртуальной таблице, но это часто может быть приемлемой потерей.

6 голосов
/ 20 июля 2011

Резюме: не делай этого.

Ответ j_random_hacker говорит вам , как сделать это. Тем не менее, я также хотел бы отметить, что вы должны , а не сделать это. Суть шаблонов в том, что они могут принимать любой совместимый тип, и ограничения типа стиля Java нарушают его.

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

C ++, с другой стороны, не имеет такого ограничения. Типы параметров шаблона могут быть любого типа, совместимого с операциями, с которыми они используются. Там не должно быть общего базового класса. Это похоже на «Duck Typing» в Python, но выполняется во время компиляции.

Простой пример, демонстрирующий силу шаблонов:

// Sum a vector of some type.
// Example:
// int total = sum({1,2,3,4,5});
template <typename T>
T sum(const vector<T>& vec) {
    T total = T();
    for (const T& x : vec) {
        total += x;
    }
    return total;
}

Эта функция суммирования может суммировать вектор любого типа, который поддерживает правильные операции. Он работает как с примитивами типа int / long / float / double, так и с пользовательскими числовыми типами, которые перегружают оператор + =. Черт возьми, вы даже можете использовать эту функцию для объединения строк, так как они поддерживают + =.

Бокс / распаковка примитивов не требуется.

Обратите внимание, что он также создает новые экземпляры T, используя T (). Это тривиально в C ++ с использованием неявных интерфейсов, но не совсем возможно в Java с ограничениями типов.

Хотя шаблоны C ++ не имеют явных ограничений типов, они по-прежнему безопасны для типов и не будут компилироваться с кодом, который не поддерживает правильные операции.

5 голосов
/ 12 декабря 2013
class Base
{
    struct FooSecurity{};
};

template<class Type>
class Foo
{
    typename Type::FooSecurity If_You_Are_Reading_This_You_Tried_To_Create_An_Instance_Of_Foo_For_An_Invalid_Type;
};

Убедитесь, что производные классы наследуют структуру FooSecurity, и компилятор расстроится во всех нужных местах.

5 голосов
/ 17 мая 2009

Это невозможно в простом C ++, но вы можете проверить параметры шаблона во время компиляции с помощью Concept Checking, например используя BCCL Boost's .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...