При проектировании стандартной библиотеки C++ было принято множество странных решений, из-за которых приходится страдать. И исправить их не представляется возможным из-за соображений обратной совместимости.
Одним из таких странных решений являются перегрузки конструкторов с радикально различным поведением.
Яркий пример:
using namespace std::string_literals;
std::string s1 { "Modern C++", 3 };
std::string s2 { "Modern C++"s, 3 };
std::cout << "S1: " << s1 << "\n";
std::cout << "S2: " << s2 << "\n";
Этот код выведет
S1: Mod
S2: ern C++
Потому что у std::basic_string
есть один конструктор, принимающий указатель и длину строки.
А есть еще один конструктор, принимающий «что-то похожее на строку» и позицию, с которой надо из нее извлечь подстроку!
На этом причуды не заканчиваются.
std::string s1 {'H', 3};
std::string s2 {3, 'H'};
std::string s3 (3, 'H');
std::cout << "S1: " << s1.size() << "\n";
std::cout << "S2: " << s2.size() << "\n";
std::cout << "S3: " << s3.size() << "\n";
Этот пример выведет
S1: 2
S2: 2
S3: 3
Потому что у строки есть конструктор, принимающий число n
и символ c
, который
нужно повторить n
раз. А еще есть конструктор, принимающий список инициализации (std::initializer_list<T>
), состоящий из символов. И существование этого конструктора взаимодействует с неявным приведением типов!
std::string s1 {'H', 3};
— строка "H\3"std::string s2 {3, 'H'};
— строка "\3H"std::string s3 (3, 'H');
— строка "HHH"
Аналогичной проблемой страдает std::vector
std::vector<int> v1 {3, 2}; // v1 == {3, 2}
std::vector<int> v2 (3, 2); // v2 == {2,2,2}
А еще у контейнеров есть конструктор, принимающий пару итераторов. И, казалось бы, с ними уж проблем-то не будет, но у нас есть указатели, которые также являются итераторами. А еще есть тип bool
:
bool array[5] = {true, false, true, false, true};
std::vector<bool> vector {array, array + 5};
std::cout << vector.size() << "\n";
Будет выведено 2, а не 5. Потому что указатели неявно приводятся к bool
!
Собственно, эти прекрасные примеры показывают, почему «универсальная» инициализация не универсальная.
Чтобы не множить хаос в своих проектах, нужно быть осторожнее с объявлениями перегруженных конструкторов для своих типов. Лучше ввести статическую функцию, чем создавать перегруженные конструкторы, неожиданно взаимодействующие с неявным приведением типов и списками инициализации.