Преобразование конструктора с несколькими аргументами

В С++ 11 конструктор без explicit ключевого слова может использоваться для неявного преобразования списка аргументов в его класс. Например:

class Date{
private:
  int d, m, y;
public:
  Date(int _d, int _m=0, int _y=0) : m(_m), d(_d), y(_y) {}
  friend bool operator==(const Date &x, const Date &y) {return  x.d==y.d;}
};

int main()
{
  Date x = {1,2,3}; // no error; using converting constructor
  x == 1; // no error; converting constructor turns int into Date object
  x == {1,2,3}; // error
}

Для x == {1,2,3} я получил следующую ошибку:

explicit.cc:16:10: error: expected primary-expression before ‘{ token
       x=={1,2,3};
          ^

Мне интересно, почему преобразование конструктора не конвертирует список {1,2,3} в объект Date? Тем более, что x == 1 не приводит к ошибке, почему x == {1,2,3}?

Ответ 1

Вы можете быть особенно удивлены тем, что:

x = {1, 2, 3};            // ok
x == {1, 2, 3};           // error
operator==(x, {1, 2, 3}); // ok

Это связано с тем, что есть только определенные места, где бит-init-список (в основном, разделенный запятой список вещей между {}s) разрешен для входа на язык. Он может идти по правой стороне = потому что правила говорят, что это возможно. Он может использоваться в качестве аргумента в выражении вызова функции, поскольку правила говорят, что это возможно. Но он не может использоваться по обе стороны операторов сравнения, потому что правила этого не позволяют.

Я не знаю, есть ли основополагающая причина этого за пределами, возможно, для этого не существует большой потребности.

Ответ 2

Мне интересно, почему преобразование конструктора не конвертирует список {1,2,3} в объект Date?

Потому что это не "конвертирующий конструктор". Это просто "конструктор".

Инициализация списка (то, что происходит, когда вы используете бит-init-list) используется для инициализации объекта из списка значений, как можно было бы ожидать от имени. x = {1, 2, 3}; не инициализирует объект. x - объект, который уже был инициализирован.

Таким образом, скопированные-init-списки не могут быть непосредственно применены к существующему объекту; они могут применяться только к объекту, который инициализируется. То, что вы хотите сделать, это использовать список для инициализации Date а затем скопировать эту Date в существующий объект x. Это записано x = Date{1, 2, 3}; ,

"Преобразующий конструктор" - это конструктор, который выполняет неявные преобразования. Неявное преобразование преобразуется из объекта одного типа в объект другого типа. Инициализация списка не является и никогда не выполнялась. Date x = {1, 2, 3}; не преобразовывает список в Date; он инициализирует Date со списком, используя правила инициализации списка копий.

Ответ 3

Чтобы завершить настройку Barry, я составил список всех операторов или выражений, в которых может появиться brace-init-list команд:

  • вызов функции: func({/*...*/},arg2)
  • subscriptip: obj[{/*...*/}];
  • явное преобразование типа: type{/*...*/}
  • новое выражение: new type{/*...*/}
  • назначение и составные назначения: a = {/*...*/}; b += {/*...*/};... a = {/*...*/}; b += {/*...*/};...
  • в условии условного оператора while (atype i={/*.../*})
  • for-range-initializer for(auto it:{/*...*/})
  • return return: return {/*.../*} (если не выведен тип возвращаемого значения)
  • Инициализатор: atype a{/*...*}; atype b={/*.../*}; atype a{/*...*}; atype b={/*.../*}; или включая инициализатор члена: a_memb{/*.../*}
  • аргумент по умолчанию void f(atype a={/*.../*})