Шаблонный конструктор класса c

Обновлено: 02.05.2024

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

Определение и использование шаблонов

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

Приведенный выше код описывает шаблон для универсальной функции с одним параметром типа T, возвращаемое значение и параметры вызова (lhs и rhs) всех этих типов. Вы можете присвоить параметру типа любое имя, но чаще всего используются отдельные прописные буквы. T — параметр шаблона; ключевое typename слово означает, что этот параметр является заполнителем для типа. При вызове функции компилятор заменит каждый экземпляр T конкретным аргументом типа, заданным пользователем или выведенным компилятором. Процесс, в котором компилятор создает класс или функцию из шаблона, называется экземпляром шаблона; minimum — это создание экземпляра шаблона minimum .

В другом месте пользователь может объявить экземпляр шаблона, специализированного для int. Предположим, что get_a() и get_b() являются функциями, возвращающими целое число:

Тем не менее, поскольку это шаблон функции, и компилятор может вывести тип T из аргументов a и b, его можно вызвать так же, как обычная функция:

При обнаружении последней инструкции компилятор создает новую функцию, в которой каждое вхождение T в шаблоне заменяется следующим int :

Правила того, как компилятор выполняет вычет типов в шаблонах функций, основаны на правилах для обычных функций. Дополнительные сведения см. в разделе " Разрешение перегрузки вызовов шаблонов функций".

Параметры типа

В приведенном выше шаблоне minimum обратите внимание, что параметр типа T не является полным, пока он не будет использоваться в параметрах вызова функции, где добавляются константы и ссылочные квалификаторы.

Практические ограничения на количество параметров типа не ограничено. Разделите несколько параметров запятыми:

Ключевое слово class эквивалентно этому контексту typename . Предыдущий пример можно выразить следующим образом:

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

Любой встроенный или определяемый пользователем тип можно использовать в качестве аргумента типа. Например, можно использовать std::vector в стандартной библиотеке для хранения переменных типа int , double std::string, MyClass const MyClass *, MyClass& и т. д. Основное ограничение при использовании шаблонов заключается в том, что аргумент типа должен поддерживать любые операции, применяемые к параметрам типа. Например, если мы вызываем minimum использование MyClass , как показано в следующем примере:

Ошибка компилятора будет создана, так как MyClass не предоставляет перегрузку для оператора.

Нет никаких обязательных требований к тому, что аргументы типа для любого конкретного шаблона принадлежат к одной иерархии объектов, хотя можно определить шаблон, который применяет такое ограничение. Вы можете сочетать объектно-ориентированные методы с шаблонами; Например, можно сохранить производный* в vectorBase. Обратите внимание, что аргументы должны быть указателями

Базовые требования, которые std::vector и другие контейнеры стандартной T библиотеки накладывают на элементы, которые T можно скопировать и сконструировать.

Параметры, не относящиеся к типу

Обратите внимание на синтаксис в объявлении шаблона. Значение size_t передается в качестве аргумента шаблона во время компиляции и должно быть const или выражением constexpr . Используйте его следующим образом:

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

Вычет типов для параметров шаблона, не относящихся к типу

В Visual Studio 2017 и более поздних версиях, а также в /std:c++17 режиме или более поздних версиях компилятор выводит тип аргумента шаблона, не относящееся к типу, объявленному с помощью auto :

Шаблоны в качестве параметров шаблона

Шаблон может быть параметром шаблона. В этом примере MyClass2 имеет два параметра шаблона: параметр typename T и параметр шаблона Arr:

Так как сам параметр Arr не имеет основного текста, его имена параметров не требуются. На самом деле, это ошибка для ссылки на имя типа или имена параметров класса Arr из текста MyClass2 . По этой причине имена параметров типа Arr могут быть опущены, как показано в следующем примере:

Аргументы шаблона по умолчанию

Шаблоны классов и функций могут иметь аргументы по умолчанию. Если у шаблона есть аргумент по умолчанию, его можно оставить неуказаным при его использовании. Например, шаблон std::vector имеет аргумент по умолчанию для распределителя:

В большинстве случаев класс std::allocator по умолчанию является допустимым, поэтому используется вектор следующим образом:

Но при необходимости можно указать пользовательский распределитель следующим образом:

При наличии нескольких аргументов шаблона все аргументы после первого аргумента по умолчанию должны иметь аргументы по умолчанию.

При использовании шаблона, параметры которого используются по умолчанию, используйте пустые угловые скобки:

Специализация шаблонов

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

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

Давайте представим себе, что нам нужно написать набор функций, которые отличаются друг от друга лишь парой ключевых слов (и, как правило, одно из них — название типа). Ну, вот, например, взгляните на функции, рассчитывающие суммы элементов массивов для разных типов (упрощения ради, проверки указателей на неравенство нулю опущены /*упрощения ради также не рассматривается возможность переполнения для int — прим. пер.*/)


Ну согласитесь же, насколько бы было лучше описать тело функции один раз, указав названия принимаемого (и возвращаемого) типа в виде «параметра», а потом определить экземпляры функции для конкретных типов? И эти функции еще относительно просты, а представьте себе, если бы они были длиннее, а их набор — больше.

Вот именно для этого случая в C++ существует ключевое слово template. Но увы, не в чистом С.

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

Шаблоны в С.

Нам понадобятся некоторые ингридиенты.

1: Заготовки

Для начала, определим пару макросов. Они будут располагаться в отдельном заголовочном файле, и этот файл нам еще понадобится. Для ясности, назовем этот отдельный заголовочный файл «templates.h»

Макрос Template нам понадобится в дальнейшем чтобы объединять макроопределения X и Y в виде X_Y, таким образом, чтобы написав TEMPLATE(function,type) мы получили бы в этом месте function_type.

2: Готовим
3. Сервируем.

Не помню, сколько строк мы до этого написали, но для резюме вы смело можете умножить их число на 3. Или 4?

Ну, и Хииииииииидер!


Теперь должно быть понятно, почему мы не защищали sum_as_template.h от множественных включений: мы включаем его по разу на каждый задействованный тип.

4. Подаем

Ну, собственно, и все. Можно вызывать:

Пытливый читатель спросит переводчика, а что если мне нужен тип «unsigned long long»? Ведь у нас получится функция «void sum_unsigned long long()»? К счастью для переводчика, автор предусмотрел и это. Используйте typedef:

(Это довольно-таки вольный перевод. Свою статью писать уже лень, раз гугл знает ответ на вопрос function template in plain c, и к той статье мне решительно нечего добавить, но раз на хабре и вообще в русскоязычном секторе ответ гуглом не находится, чтобы добру не пропадать, опубликую пост-мортем)

UPD: Хочу сказать огромное спасибо хабраюзеру GRAFIN99 за как минимум 4 выявленные ошибки в исходниках, причем три из них — на глаз, просто в ходе прочтения статьи.

I wish to have a non-template class with a template constructor with no arguments.

As far as I understand, it's impossible to have it (because it would conflict with the default constructor - am I right?), and the workaround is the following:

Maybe there is a better alternative for this (or a better workaround)?

I would echo Johannes question. Why? There could be a better technique if we understand what you are trying to do.

@Loki It would be nice to have if generating something from a sequence of inputs (like vector's templated iterator constructor).

@VF1 Exactly why I came here. My class holds a vector of enums and I'd like to initialize it with a sequence of some kind. A vector seems heavy-weight, particularly pre-brace-init (C++98 here). VAR_ARGS seems just terrible (even though it may be best). Passing a reference to an array seems ok.

9 Answers 9

You can now choose to sort by Trending, which boosts votes that have happened recently, helping to surface more up-to-date answers.

Trending is based off of the highest score sort and falls back to it if no posts are trending.

There is no way to explicitly specify the template arguments when calling a constructor template, so they have to be deduced through argument deduction. This is because if you say:

The is the template argument list for the type Foo , not for its constructor. There's nowhere for the constructor template's argument list to go.

Even with your workaround you still have to pass an argument in order to call that constructor template. It's not at all clear what you are trying to achieve.

But automatic type inference is still possible. As long as you are satisfied with automatic type inference, you can use a template constructor (of a non-template class).

@updogliu: Absolutely. But, the question is asking about "a template constructor with no arguments" If there are no function arguments, no template arguments may be deduced.

You could use a templated factory function instead of a constructor:

@Martin: I think this could also return by value (a non-pointer). RVO should take care of eliminating the copy anyway.

create() does not have to do dynamic allocation. Just return Foo(. ); Thanks @Samuel_xL. This turned out to be a great idea for me.

Unfortunately, factory methods do not return xref values.. xref can be used with && with additional std::move.. if they did/could..

I don't get. How does it call a templated constructor with an empty parameters list? It seems to call regular one.

As far as I understand, it's impossible to have it (because it would conflict with the default constructor - am I right?)

You are wrong. It doesn't conflict in any way. You just can't call it ever.

1. Actually, you can call the template ctor: int *p; A a(p) . 2. Prejudice or idealization ;-) There is no John Doe. There is nothing typical German. and definitely not in Johannes' words. If they sound humorless then just because C++ is humorless.

@AndreasSpindler I meant that he can never call the constructor if it has no arguments but still a template parameter (applies only to C++03. C++11 has introduced default template parameter, which would allow calling it, of course, if the ctor has default arguments..).

the above helpers let you work with types as values.

the tag type is a variable with no state besides the type it caries. You can use this to pass a pure-type value into a template function and have the type be deduced by the template function:

You can pass in more than one type:

  • If you declare any constructor(including a templated one), the compiler will refrain from declaring a default constructor.
  • Unless you declare a copy-constructor (for class X one that takes X or X& or X const & ) the compiler will generate the default copy-constructor.
  • If you provide a template constructor for class X which takes T const & or T or T& then the compiler will nevertheless generate a default non-templated copy-constructor, even though you may think that it shouldn't because when T = X the declaration matches the copy-constructor declaration.
  • In the latter case you may want to provide a non-templated copy-constructor along with the templated one. They will not conflict. When X is passed the nontemplated will be called. Otherwise the templated

Funny thing is that if you declare sometihng like template X(const X&); then it still will not be recognized as copy constructor, and standard copy-constructor will be created.

. and default ctors. A class declared as struct S < templateS() < >>; declares an "unreachable ctor". There is no way to deduce or pass the T . Nonetheless a default ctor is not generated since a user-defined ctor seems to exist.

You could do this:

Then to create an object of type C using int as the template parameter to the constructor:

Since you can't pass template parameters to a constructor, this solution essentially converts the template parameter to a regular parameter. Using the UseType() function when calling the constructor makes it clear to someone looking at the code that the purpose of that parameter is to tell the constructor what type to use.

One use case for this would be if the constructor creates a derived class object and assigns it to a member variable that is a base class pointer. (The constructor needs to know which derived class to use, but the class itself doesn't need to be templated since the same base class pointer type is always used.)

I read that template copy-con is never default copy onstructor, and template assignment-op is never a copy assignment operator.

I couldn't understand why this restriction is needed and straight away went online to ideone and return a test program but here copy constructor never gets called on further googling I came across templatized constructor and tried that but still it never calls copy constructor.

Can someone explain me the whole reason behind putting this restriction and also how to write a copy constructor of template class.

I edited code and ran it in ideone but still copy constructor do not get called. but I also not able to understand reason behind this restriction

2 Answers 2

You can now choose to sort by Trending, which boosts votes that have happened recently, helping to surface more up-to-date answers.

Trending is based off of the highest score sort and falls back to it if no posts are trending.

I can't comment on why this is how it is, but here's how you write a copy constructor and assignment operator for a class template:

and that's it.
The trick here is that even though A is a template, when you refer to it inside the class as A (such as in the function signatures) it is treated as the full type A .

@Daniel That's because A also refers to the full type of the class, A is just a shorthand you can use inside the class.

There are strict rules what constitutes a copy constructor (cf. C++11, 12.8):

It is not a template.

For a class T , its first argument must have type T & or T const & or T volatile & or T const volatile & .

If it has more than one argument, the further arguments must have default values.

If you do not declare a copy constructor, a copy constructor of the form T::T(T const &) is implicitly declared for you. (It may or may not actually be defined, and if it is defined it may be defined as deleted.)

(The usual overload resolution rules imply that you can have at most four copy constructors, one for each CV-qualification.)

В этом разделе описываются правила, относящиеся к шаблонам классов C++.

Функции-члены шаблонов классов

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

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

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

Шаблоны вложенных классов

Шаблоны можно определить в классах или шаблонах классов (в этом случае они называются шаблонами членов). Шаблоны членов, которые являются классами, называются шаблонами вложенных классов. Шаблоны элементов, которые являются функциями, рассматриваются в шаблонах функций-членов.

Шаблоны вложенных классов объявляются как шаблоны классов внутри области внешнего класса. Их можно определить во включающем классе или вне его.

В следующем примере кода демонстрируется шаблон вложенного класса внутри обычного класса.

Локальные классы не могут иметь шаблоны элементов.

Друзья-шаблоны

Шаблоны классов могут иметь друзей. Дружественными объектами класса-шаблона могут быть классы или шаблоны классов, функции или шаблоны функций. Ими также могут быть специализации (кроме частичных) шаблонов классов или шаблонов функций.

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

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

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

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

Повторное использование параметров шаблона

Параметры шаблона могут повторно использования в списке параметров шаблона. Например, приведенный ниже код допустим:

Читайте также: