Шаблоны C ++, которые принимают только определенные типы

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

public class ObservableList { ... } 

Это делается с использованием ключевого слова «extends».

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

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

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

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

 template class my_template; // Declare, but don't define // int is a valid type template<> class my_template { ... }; // All pointer types are valid template class my_template { ... }; // All other types are invalid, and will cause linker error messages. 

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

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

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

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

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

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

 #include  template struct void_ { using type = void; }; template using Void = typename void_::type; template struct has_const_iterator : std::false_type {}; template struct has_const_iterator> : std::true_type {}; struct has_begin_end_impl { template().begin()), typename End = decltype(std::declval().end())> static std::true_type test(int); template static std::false_type test(...); }; template struct has_begin_end : decltype(has_begin_end_impl::test(0)) {}; template class observable_list { static_assert(has_const_iterator::value, "Must have a const_iterator typedef"); static_assert(has_begin_end::value, "Must have begin and end member functions"); // code here... }; 

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

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

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

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

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

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

 #include  #include  class Base {}; class Derived: public Base {}; #if 0 // wrapper template  class MyClass /* where T:Base */ { private: static_assert(std::is_base_of::value, "T is not derived from Base"); typename std::enable_if::value, T>::type inner; }; #elif 0 // base class template  class MyClass: /* where T:Base */ protected std::enable_if::value, T>::type { private: static_assert(std::is_base_of::value, "T is not derived from Base"); }; #elif 1 // list-of template  class MyClass /* where T:list */ { static_assert(std::is_base_of::value , "T::value_type is not derived from Base"); typedef typename std::enable_if::value, T>::type base; typedef typename std::enable_if::value, T>::type::value_type value_type; }; #endif int main() { #if 0 // wrapper or base-class MyClass derived; MyClass base; // error: MyClass wrong; #elif 1 // list-of MyClass> derived; MyClass> base; // error: MyClass> wrong; #endif // all of the static_asserts if not commented out // or "error: no type named 'type' in 'struct std::enable_if' pointing to: // 1. inner // 2. MyClass // 3. base + value_type } 

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

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

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

 template::value>::type* = nullptr> class ObservableList { // ... }; 

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

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

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

C ++ также поддерживает подclassы.

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

Давайте обернем его в typedef, чтобы упростить его использование, а не сделать целый class, et voila:

typedef std::list subclasses_of_superclass_only_list;

Например:

 class Shape { }; class Triangle : public Shape { }; typedef std::list 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 ++ был бы просто абстрактным classом, то есть classом, который реализует только чистые виртуальные методы. Используя этот метод, вы можете легко реализовать свой Java-пример на C ++ без каких-либо концепций или специализированных шаблонов. Он также будет работать так же медленно, как генерические стили Java, из-за виртуального поиска таблиц, но это часто может быть приемлемой потерей.

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

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

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

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

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

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

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

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

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

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

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

 class Base { struct FooSecurity{}; }; template class Foo { typename Type::FooSecurity If_You_Are_Reading_This_You_Tried_To_Create_An_Instance_Of_Foo_For_An_Invalid_Type; }; 

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

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

Нет.

В зависимости от того, что вы пытаетесь выполнить, могут быть адекватные (или даже лучшие) заменители.

Я просмотрел некоторый код STL (в Linux, я думаю, что это тот, который вытекает из реализации SGI). Он имеет «концептуальные утверждения»; например, если вам нужен тип, который понимает *x и ++x , это утверждение концепции будет содержать этот код в функции do-nothing (или что-то подобное). Это требует некоторых накладных расходов, поэтому было бы разумно поместить его в макрос, определение которого зависит от #ifdef debug .

Если отношение подclassа действительно то, о чем вы хотите знать, вы можете утверждать в конструкторе, что T instanceof list (за исключением того, что он написан «по-разному» на C ++). Таким образом, вы можете проверить свой выход из компилятора, не имея возможности проверить его для вас.

Для таких проверок типа нет ключевого слова, но вы можете поместить некоторый код, который по крайней мере потерпит неудачу:

(1) Если вы хотите, чтобы шаблон функции принимал только параметры определенного базового classа X, назначьте ему ссылку X в своей функции. (2) Если вы хотите принимать функции, но не примитивы или наоборот, или хотите фильтровать classы другими способами, вызовите (пустую) вспомогательную функцию шаблона внутри вашей функции, которая определена только для classов, которые вы хотите принять.

Вы можете использовать (1) и (2) также в функциях-членах classа, чтобы заставить эти проверки типа для всего classа.

Вы можете, вероятно, поместить его в какой-нибудь умный Macro, чтобы облегчить вашу боль. 🙂

Ну, вы можете создать свой шаблон, читающий что-то вроде этого:

 template class ObservableList { std::list contained_data; }; 

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

Насколько я знаю, в текущем стандарте не существует конструкции, которая бы полностью отражала оператор оператора Java в полном объеме.

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

В C ++ 11 введение понятий должно сделать это проще, но я не думаю, что он будет делать именно то, что вы хотите.

  • Как определить, существует ли конкретная переменная-член в classе?
  • Как получить текущий URL-адрес в шаблоне Django?
  • Преимущества авто в параметрах шаблона в C ++ 17
  • HTML5: Почему мой атрибут oninvalid позволяет шаблону выйти из строя?
  • heredoc для Windows?
  • Почему реализация и объявление classа шаблона должны быть в одном заголовочном файле?
  • Возrotation пустоты?
  • Вычисление и печать факториала во время компиляции в C ++
  • Можете ли вы рекомендовать механизм шаблонов .net?
  • c ++ автоматическая регистрация заводских производных типов
  • Как изменить шаблоны Visual Studio для нового classа / интерфейса C #?
  • Давайте будем гением компьютера.