Может ли конструктор быть шаблонной функцией в C++?

«Может ли конструктор быть шаблонной функцией в C++?» — вопрос из категории Шаблоны, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, конструктор может быть шаблонным. Это позволяет создавать объекты, принимая аргументы различных типов, без необходимости писать множество перегруженных конструкторов. Такой конструктор часто называют "конструктором преобразования" (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;
}

Важные нюансы и ограничения:

  1. Не заменяет специальные функции-члены: Шаблонный конструктор не является конструктором копирования или перемещения. Если нужна точная передача Widget&& или const Widget&, будут вызваны автоматически сгенерированные или пользовательские конструкторы копирования/перемещения, если они объявлены.
  2. Проблема неограниченного шаблона: В примере выше шаблон слишком общий. Он может нежелательно инстанцироваться для любых типов, включая 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;
};
  1. Наследование: Шаблонные конструкторы не наследуются автоматически в производных классах. Их нужно явно «тащить» с помощью using Base::Base;.