Подтвердить что ты не робот

Почему инициализатор С++ 11 in-class не может использовать круглые скобки?

Например, я не могу написать это:

class A
{
    vector<int> v(12, 1);
};

Я могу только написать это:

class A
{
    vector<int> v1{ 12, 1 };
    vector<int> v2 = vector<int>(12, 1);
};

Что учитывает различия в языке С++ 11?

4b9b3361

Ответ 1

Одна из возможных причин заключается в том, что в скобках мы можем вернуться к наиболее неприятному анализу в кратчайшие сроки. Рассмотрим два типа ниже:

struct foo {};
struct bar
{
  bar(foo const&) {}
};

Теперь у вас есть член данных типа bar, который вы хотите инициализировать, поэтому вы определяете его как

struct A
{
  bar B(foo());
};

Но то, что вы сделали выше, объявляет функцию с именем B, которая возвращает объект bar по значению и принимает один аргумент, что функция, имеющая подпись foo() (возвращает a foo и doesn 't принимать какие-либо аргументы).

Судя по количеству и частоте вопросов, заданных в StackOverflow, которые касаются этой проблемы, это то, что большинство программистов на C++ находят удивительным и неинтуитивным. Добавление нового синтаксиса синтаксиса brace-or-equal-initializer позволило избежать этой двусмысленности и начать с чистого листа, что, вероятно, является причиной того, что комитет С++ решил сделать это.

bar B{foo{}};
bar B = foo();

Обе строки выше объявляют объект с именем B типа bar, как и ожидалось.


Помимо догадок выше, я хотел бы указать, что вы делаете две совершенно разные вещи в приведенном выше примере.

vector<int> v1{ 12, 1 };
vector<int> v2 = vector<int>(12, 1);

Первая строка инициализирует v1 вектору, который содержит два элемента: 12 и 1. Второй создает вектор v2, содержащий элементы 12, каждый из которых инициализируется символом 1.

Будьте осторожны с этим правилом - если тип определяет конструктор, который принимает initializer_list<T>, тогда этот конструктор всегда считается первым, когда инициализатор для этого типа является скоординированным списком инициализации. Другие конструкторы будут рассмотрены только в том случае, если один, принимающий initializer_list, не является жизнеспособным.

Ответ 2

Обоснование этого выбора явно упоминается в связанном предложении для инициализаторов нестационарных элементов данных:

Проблема, поднятая в Kona относительно объема идентификаторов:

В ходе обсуждения в Основной рабочей группе на заседании 07 сентября в Коне возник вопрос об объеме идентификаторов в инициализаторе. Мы хотим разрешить класс scope с возможностью прямого поиска; или мы хотим потребовать, чтобы инициализаторы были четко определены в точке, в которой они были проанализированы?

Желательно:

Мотивация для поиска в классе-классе заключается в том, что он мог поместить что-либо в инициализатор нестатических данных, который мы могли бы поместить в mem-инициализатор без существенного изменения семантики (по модулю прямой инициализации и инициализации копирования ):

int x();

struct S {
    int i;
    S() : i(x()) {} // currently well-formed, uses S::x()
    // ...
    static int x();
};

struct T {
    int i = x(); // should use T::x(), ::x() would be a surprise
    // ...
    static int x();
};

Проблема 1:

К сожалению, это делает инициализаторы формы "(выражение-список)" неоднозначными в момент разбора объявления:

   struct S {
        int i(x); // data member with initializer
        // ...
        static int x;
    };

    struct T {
        int i(x); // member function declaration
        // ...
        typedef int x;
    };

Одно из возможных решений заключается в том, чтобы полагаться на существующее правило, которое, если объявление может быть объектом или функцией, тогда его функция:

 struct S {
        int i(j); // ill-formed...parsed as a member function,
                  // type j looked up but not found
        // ...
        static int j;
    };

Аналогичным решением было бы применить другое существующее правило, которое в настоящее время используется только в шаблонах, если T может быть типом или чем-то другим, то это нечто другое; и мы можем использовать "typename", если мы действительно имеем в виду тип:

struct S {
        int i(x); // unabmiguously a data member
        int j(typename y); // unabmiguously a member function
    };

Оба этих решения вводят тонкости, которые многие пользователи могут недопонимать (о чем свидетельствует множество вопросов о comp.lang.С++ о том, почему "int i();" в области блока не объявляет инициализацию по умолчанию int).

Решение, предлагаемое в этой статье, состоит в том, чтобы разрешить только инициализаторы форм "= initializer-clause" и "{initializer-list}" . Это решает проблему двусмысленности в большинстве случаях, например:

HashingFunction hash_algorithm{"MD5"};

Здесь мы не могли использовать форму =, потому что конструктор HasningFunctions является явным. В особенно сложных случаях, тип, возможно, придется упомянуть дважды. Рассмотрим:

   vector<int> x = 3; // error:  the constructor taking an int is explicit
   vector<int> x(3);  // three elements default-initialized
   vector<int> x{3};  // one element with the value 3

В этом случае мы должны выбрать между двумя альтернативами, используя соответствующие обозначения:

vector<int> x = vector<int>(3); // rather than vector<int> x(3);
vector<int> x{3}; // one element with the value 3

Проблема 2:

Другая проблема заключается в том, что, поскольку мы не предлагаем никаких изменений в правилах инициализации статических элементов данных, добавление статического ключевого слова может привести к неправильному формированию правильно сформированного инициализатора:

   struct S {
               const int i = f(); // well-formed with forward lookup
        static const int j = f(); // always ill-formed for statics
        // ...
        constexpr static int f() { return 0; }
    };

Проблема 3:

Третья проблема заключается в том, что поиск по классам может превратить ошибку времени компиляции во временную ошибку:

struct S {
    int i = j; // ill-formed without forward lookup, undefined behavior with
    int j = 3;
};

(Если не пойман компилятором, я мог бы быть проиндексирован с помощью значения undefined j.)

Предложение:

У CWG был опрос соломы от 6 до 3 в Kona в пользу поиска класса; и это то, что предлагает этот документ, с инициализаторами для нестатических членов данных, ограниченными формами "= initializer-clause" и "{initializer-list".

Мы считаем:

Проблема 1: эта проблема не возникает, поскольку мы не предлагаем нотацию(). Обозначения инициализатора = и {} не страдают от этой проблемы.

Проблема 2: добавление ключевого слова static делает ряд различий, это наименьшее из них.

Проблема 3: это не новая проблема, но такая же проблема с порядком инициализации, которая уже существует с инициализаторами конструктора.