Почему существуют правила перегрузки конструктора 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;
}
Другие вопросы по тегам