Ответ
Да, конструктор может быть шаблонным. Это позволяет создавать объекты, принимая аргументы различных типов, без необходимости писать множество перегруженных конструкторов. Такой конструктор часто называют "конструктором преобразования" (converting constructor).
Простой пример:
#include <iostream>
#include <string>
class Widget {
std::string data;
public:
// Шаблонный конструктор
template <typename T>
Widget(const T& value) : data(std::to_string(value)) {
std::cout << "Widget constructed from type: " << typeid(T).name() << 'n';
}
// Обычный конструктор для строк
Widget(const std::string& s) : data(s) {
std::cout << "Widget constructed from stringn";
}
};
int main() {
Widget w1(42); // T = int
Widget w2(3.14); // T = double
Widget w3("text"); // Вызовет конструктор для std::string (он предпочтительнее шаблонного для const char*)
return 0;
}
Важные нюансы и ограничения:
- Не заменяет специальные функции-члены: Шаблонный конструктор не является конструктором копирования или перемещения. Если нужна точная передача
Widget&&илиconst Widget&, будут вызваны автоматически сгенерированные или пользовательские конструкторы копирования/перемещения, если они объявлены. - Проблема неограниченного шаблона: В примере выше шаблон слишком общий. Он может нежелательно инстанцироваться для любых типов, включая
Widget(при попытке копирования), что может привести к рекурсии. Это решается с помощью SFINAE или, начиная с C++20, концептов.
Пример с концептом (C++20) для ограничения типов:
template <typename T>
concept ConvertibleToString = requires(T a) {
{ std::to_string(a) } -> std::same_as<std::string>;
};
class SafeWidget {
std::string data;
public:
// Конструктор только для типов, конвертируемых в строку через std::to_string
template <ConvertibleToString T>
explicit SafeWidget(const T& value) : data(std::to_string(value)) {}
// Конструктор копирования (не шаблонный!)
SafeWidget(const SafeWidget&) = default;
};
- Наследование: Шаблонные конструкторы не наследуются автоматически в производных классах. Их нужно явно «тащить» с помощью
using Base::Base;.