В Java вы можете определить универсальный класс, который принимает только те типы, которые расширяют класс по вашему выбору, например:
public class ObservableList<T extends List> {
...
}
Это делается с помощью ключевого слова "extends".
Есть ли какой-нибудь простой эквивалент этого ключевого слова в C ++?
Ответы:
Я предлагаю использовать функцию статического подтверждения Boost совместно с
is_base_of
библиотекой Boost Type Traits:В некоторых других, более простых случаях вы можете просто объявить глобальный шаблон вперед, но только определить (явно или частично специализировать) его для допустимых типов:
[Незначительное изменение 6/12/2013: использование объявленного, но не определенного шаблона приведет к сообщениям об ошибках компоновщика , а не компилятора.]
источник
myBaseType
точно. Перед тем как отказаться от Boost, вы должны знать, что большая часть кода является шаблоном кода только для заголовков, поэтому во время выполнения не требуется никаких затрат памяти или времени для вещей, которые вы не используете. Также конкретные вещи, которые вы будете использовать здесь (BOOST_STATIC_ASSERT()
иis_base_of<>
), могут быть реализованы с использованием только объявлений (то есть без фактических определений функций или переменных), поэтому они не будут занимать ни места, ни времени.static_assert(std::is_base_of<List, T>::value, "T must extend list")
.my_template<int> x;
илиmy_template<float**> y;
и убедиться, что компилятор это разрешает, а затем объявить переменнуюmy_template<char> z;
и убедиться, что это не так.Как правило, это неоправданно в C ++, как отмечали другие ответы. В C ++ мы стремимся определять универсальные типы на основе других ограничений, кроме «наследуется от этого класса». Если вы действительно хотели это сделать, это довольно легко сделать в C ++ 11 и
<type_traits>
:Это нарушает многие концепции, которые люди ожидают в C ++. Лучше использовать такие приемы, как определение собственных черт. Например, может быть,
observable_list
хочет принять любой тип контейнера, который имеет typedefsconst_iterator
и функцию- членbegin
and,end
которая возвращаетconst_iterator
. Если вы ограничите это классами, которые наследуют,list
то пользователь, который имеет свой собственный тип, который не наследует,list
но предоставляет эти функции-члены и typedefs, не сможет использовать вашobservable_list
.Есть два решения этой проблемы, одно из которых - ничего не ограничивать и полагаться на типизацию утки. Большим недостатком этого решения является то, что оно включает в себя огромное количество ошибок, которые могут быть сложными для пользователей. Другое решение состоит в том, чтобы определить признаки, чтобы ограничить тип, обеспеченный, чтобы удовлетворить требованиям интерфейса. Большой минус для этого решения заключается в том, что он требует дополнительной записи, что может показаться раздражающим. Однако положительным моментом является то, что вы сможете писать свои собственные сообщения об ошибках а-ля
static_assert
.Для полноты, решение для примера выше дано:
В приведенном выше примере показано много концепций, демонстрирующих возможности C ++ 11. Некоторыми поисковыми терминами для любопытных являются шаблоны переменных, SFINAE, выражение SFINAE и черты типа.
источник
template<class T:list>
такая оскорбительная концепция. Спасибо за чаевые.Простое решение, о котором еще никто не упомянул, - просто игнорировать проблему. Если я попытаюсь использовать
int
в качестве типа шаблона в шаблоне функции, который ожидает класс контейнера, такой как вектор или список, то я получу ошибку компиляции. Грубо и просто, но это решает проблему. Компилятор попытается использовать указанный вами тип, и если это не удастся, он выдаст ошибку компиляции.Единственная проблема в том, что сообщения об ошибках, которые вы получите, будут сложно читать. Тем не менее, это очень распространенный способ сделать это. Стандартная библиотека полна шаблонов функций или классов, которые ожидают определенного поведения от типа шаблона и ничего не делают для проверки правильности используемых типов.
Если вам нужны более приятные сообщения об ошибках (или если вы хотите отлавливать случаи, которые не приводят к ошибке компилятора, но все же не имеют смысла), вы можете, в зависимости от того, насколько сложным вы хотите это сделать, использовать статическое утверждение Boost или библиотека Boost concept_check.
С современным компилятором у вас есть встроенный_интерфейс
static_assert
, который можно использовать вместо него.источник
T
и откуда этот код называется? Без некоторого контекста у меня нет шансов понять этот фрагмент кода. Но то, что я сказал, правда. Если вы попытаетесь вызватьtoString()
тип, который не имеетtoString
функции-члена, вы получите ошибку компиляции.Мы можем использовать
std::is_base_of
иstd::enable_if
:(
static_assert
могут быть удалены, вышеупомянутые классы могут быть реализованы на заказ или использованы из boost, если мы не можем ссылатьсяtype_traits
)источник
Насколько я знаю, в настоящее время это невозможно в C ++. Однако в новый стандарт C ++ 0x планируется добавить функцию, называемую «концепциями», которая обеспечивает нужную вам функциональность. Эта статья в Википедии о C ++ Concepts объяснит это более подробно.
Я знаю, что это не решает вашу непосредственную проблему, но есть некоторые компиляторы C ++, которые уже начали добавлять функции из нового стандарта, так что может быть возможно найти компилятор, который уже реализовал функцию понятий.
источник
static_assert
и SFINAE, как показывают другие ответы. Оставшаяся проблема для кого-то из Java, C # или Haskell (...) заключается в том, что компилятор C ++ 20 не выполняет проверку определений в соответствии с необходимыми понятиями, как в Java и C #.Я думаю, что все предыдущие ответы упустили из виду лес за деревьями.
Обобщения Java не совпадают с шаблонами ; они используют стирание типа , которое является динамической техникой , а не полиморфизм времени компиляции , который является статической техникой . Должно быть очевидно, почему эти две очень разные тактики плохо склеиваются.
Вместо того, чтобы пытаться использовать конструкцию времени компиляции для имитации времени выполнения, давайте посмотрим, что на
extends
самом деле происходит: согласно Stack Overflow и Wikipedia , extends используется для указания подкласса.C ++ также поддерживает создание подклассов.
Вы также показывает контейнерный класс, который использует стирание типа в форме универсального и расширяет возможности для проверки типа. В C ++ вы должны самостоятельно выполнить механизм стирания типов, что очень просто: создать указатель на суперкласс.
Давайте обернем его в typedef, чтобы его было проще использовать, а не создавать целый класс и так далее:
typedef std::list<superclass*> subclasses_of_superclass_only_list;
Например:
Теперь кажется, что List - это интерфейс, представляющий собой своего рода коллекцию. Интерфейс в C ++ будет просто абстрактным классом, то есть классом, который реализует только чистые виртуальные методы. Используя этот метод, вы можете легко реализовать свой пример Java в C ++, без каких-либо концепций или специализаций шаблонов. Он также будет работать так же медленно, как универсальный стиль Java, из-за поиска в виртуальной таблице, но это часто может быть приемлемой потерей.
источник
Эквивалент, который принимает только типы T, производные от типа List, выглядит так
источник
Резюме: не делай этого.
Ответ j_random_hacker говорит вам, как это сделать. Однако я также хотел бы отметить, что вы не должны этого делать. Суть шаблонов в том, что они могут принимать любой совместимый тип, и ограничения типа стиля Java нарушают его.
Типовые ограничения Java - это ошибка, а не функция. Они существуют потому, что Java стирает типы в обобщенных типах, поэтому Java не может понять, как вызывать методы, основываясь только на значении параметров типа.
С ++, с другой стороны, не имеет такого ограничения. Типы параметров шаблона могут быть любого типа, совместимого с операциями, с которыми они используются. Там не должно быть общего базового класса. Это похоже на «Duck Typing» в Python, но сделано во время компиляции.
Простой пример, демонстрирующий силу шаблонов:
Эта функция суммы может суммировать вектор любого типа, который поддерживает правильные операции. Он работает как с примитивами типа int / long / float / double, так и с пользовательскими числовыми типами, которые перегружают оператор + =. Черт возьми, вы даже можете использовать эту функцию для объединения строк, так как они поддерживают + =.
Бокс / распаковка примитивов не требуется.
Обратите внимание, что он также создает новые экземпляры T, используя T (). Это тривиально в C ++ с использованием неявных интерфейсов, но на самом деле невозможно в Java с ограничениями типов.
Хотя шаблоны C ++ не имеют явных ограничений типов, они по-прежнему безопасны для типов и не будут компилироваться с кодом, который не поддерживает правильные операции.
источник
Это невозможно в простом C ++, но вы можете проверить параметры шаблона во время компиляции с помощью Concept Checking, например, используя Boost BCCL .
Начиная с C ++ 20 концепции становятся официальной чертой языка.
источник
Убедитесь, что производные классы наследуют структуру FooSecurity, и компилятор расстроится во всех нужных местах.
источник
Type::FooSecurity
используется в шаблонном классе. Если класс, переданный в аргументе шаблона, не имеетFooSecurity
, попытка его использования вызывает ошибку. Он уверен, что если класс, переданный в аргументе шаблона, не имеет FooSecurity, он не является производным отBase
.Использование концепции C ++ 20
https://en.cppreference.com/w/cpp/language/constraints cppreference приводит пример использования наследования в качестве примера явной концепции:
Для нескольких баз, я предполагаю, что синтаксис будет:
GCC 10, кажется, реализовал это: https://gcc.gnu.org/gcc-10/changes.html, и вы можете получить его как PPA на Ubuntu 20.04 . https://godbolt.org/ Мой локальный GCC 10.1 еще не распознал
concept
, поэтому не уверен, что происходит.источник
Нет.
В зависимости от того, что вы пытаетесь достичь, могут быть адекватные (или даже лучшие) заменители.
Я просмотрел некоторый код STL (в Linux я думаю, что он является производным от реализации SGI). Имеет «концептуальные утверждения»; например, если вам требуется тип, который понимает
*x
и++x
, утверждение концепции будет содержать этот код в функции «ничего не делать» (или что-то подобное). Это требует некоторых накладных расходов, поэтому было бы разумно поместить его в макрос, от определения которого зависит#ifdef debug
.Если отношение подкласса действительно то, о чем вы хотите знать, вы можете заявить об этом в конструкторе
T instanceof list
(за исключением того, что оно написано по-другому в C ++). Таким образом, вы можете проверить свой выход из компилятора, не имея возможности проверить его за вас.источник
Для таких проверок типов нет ключевого слова, но вы можете поместить в него некоторый код, который, по крайней мере, упадет по порядку:
(1) Если вы хотите, чтобы шаблон функции принимал только параметры определенного базового класса X, присвойте ему ссылку X в вашей функции. (2) Если вы хотите принимать функции, но не примитивы или наоборот, или вы хотите фильтровать классы другими способами, вызовите (пустую) вспомогательную функцию шаблона в вашей функции, которая определена только для классов, которые вы хотите принять.
Вы можете использовать (1) и (2) также в функциях-членах класса для принудительной проверки типов во всем классе.
Вы можете поместить его в какой-нибудь умный макрос, чтобы облегчить боль. :)
источник
Ну, вы можете создать свой шаблон, читая что-то вроде этого:
Это, однако, сделает ограничение неявным, плюс вы не сможете просто предоставить что-то похожее на список. Существуют и другие способы ограничения используемых типов контейнеров, например, путем использования определенных типов итераторов, которые существуют не во всех контейнерах, но опять-таки это неявное, а не явное ограничение.
Насколько мне известно, в текущем стандарте не существует конструкции, которая в полной мере отражала бы оператор Java оператора.
Есть способы ограничить типы, которые вы можете использовать в шаблоне, который вы пишете, с помощью определенных typedefs внутри вашего шаблона. Это обеспечит сбой при компиляции специализации шаблона для типа, который не включает этот конкретный typedef, поэтому вы можете выборочно поддерживать / не поддерживать определенные типы.
В C ++ 11 введение концепций должно упростить это, но я не думаю, что оно будет делать именно то, что вы хотели бы.
источник