Почему существуют правила перегрузки конструктора C++11 std::initializer_list?
Я не могу думать ни найти, ни найти оправдание для следующего кода:
std::vector<int> a{1,2} // calls (7)
std::vector<int> a(1,2) // calls (2)
// constructors from http://en.cppreference.com/w/cpp/container/vector/vector
vector( std::initializer_list<T> init,
const Allocator& alloc = Allocator() ); // (7)
explicit vector( size_type count,
const T& value = T(),
const Allocator& alloc = Allocator()); // (2)
Различные функции вызываются в зависимости от того, какой метод построения вы используете ({}
против ()
) и это кажется мне крайне извращенным. Почему std::initializer_list
предпочтительнее других функций, которые в противном случае идеально соответствовали бы заданным параметрам? Я знаю этого конструктора 2
выше было объявлено устаревшим в C++11, вероятно, из-за этого изменения, но я до сих пор не могу понять, почему это так. Единственное преимущество, которое я вижу для этого поведения, состоит в том, что вы можете инициализировать контейнер с определенными значениями и требовать только одну пару фигурных скобок; std::vector<int> a{1,2}
против std::vector<int> a{{1,2}}
, Но, по крайней мере для меня, это, безусловно, не перевешивает двусмысленность и изменения, вызванные этим разрешением перегрузки. По словам Скотта Мейерса из Effective Modern C++, std::make_unique
а также std::make_shared
необходимо явно указать, какая форма используется для построения как часть их интерфейса (из-за разрешения перегрузки). Это кажется смешным для меня.
Я признаю, что, должно быть, что-то упустил, но я просто не уверен, что это такое. Обратите внимание, что я только что использовал std::vector
в качестве примера, и я спрашиваю о функции в целом.
1 ответ
Это довольно забавно в C++11: причина в том, что аргументы в скобках не имеют типа. Но из этого есть исключение, и почему оно существует, мне неясно: единственная переменная "auto", которой разрешено неявно обрабатывать заключенные в скобки аргументы как список инициализатора. Но он не позволяет вернуть этот список инициализатора, если у вас есть автоматический тип функции.
Теперь вы правы: преимущество списков инициализаторов состоит в том, что вы можете инициализировать контейнер с определенными значениями. И это огромное преимущество, которое стоит изменений!
Перед списками инициализаторов создание шаблона, позволяющего инициализировать каждый класс внутри контейнера с различным значением, требовало драконовских решений, таких как получение std::vector с каждым значением или создание "пустого" класса шаблона, а затем пробивание каждого значения,
Другое дело, что списки инициализаторов позволяют вам создавать переменные функции для C++ более безопасным способом, чем использование этого страшного <cstdarg>
импортировано из C. (хотя вариационный шаблон лучше справляется с этим)
Хотите немного поиграть с комбинациями?
#include <iostream>
#include <vector>
#include <typeinfo>
using namespace std;
vector<int> func (const vector<int> &a) { //works
//auto func (const vector<int> &a) -> vector<int> { //works
//auto func (const vector<int> &a) { //don't even compile, "returns a initializer list" error
for (int i: a) {
cout << "My " << i << endl;
}
return {20 , 30};
}
int main()
{
//play with anonymous functions
auto y = [ ](vector<int> e) { return e; }; //works
vector<int> x = y({20, 30});
//auto y = [ ](){ return {20, 30}; }; //don't even compile, "returns a initializer list" error
//vector<int> x = y();
//play with initialization
//vector<int> x = {2,2,20,30}; //works
//vector<int> x{2,2,20,30}; //works
//auto x = vector<int>{2,2,20,30}; //works
//Bellow, a common mistake of people initializing a int to a auto, like auto x = { 1 }
//auto x = {2,2,20,30}; //wrong, but compiles, its a initializer list
//auto x{2,2,20,30}; //wrong, but compiles, its a initializer list
//Play with return types
//vector<int> x = func(vector<int>(2,2)); //works only with vector<int> and auto with trailing type
//vector<int> x(func(vector<int>(2,2))); //works only with vector<int> and auto with trailing type
//vector<int> x{func(vector<int>(2,2))}; //works only with vector<int> and auto with trailing type
//auto x = func(vector<int>(2,2)); //works only with vector<int> and auto with trailing type
//auto x(func(vector<int>(2,2))); //works only with vector<int> and auto with trailing type
cout << typeid(x).name() << endl;
for (int i: x) {
cout << "My " << i << endl;
}
return 0;
}