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

Инициализация структур в С++

В качестве дополнения к этому вопросу, что здесь происходит:

#include <string>
using namespace std;

struct A {
    string s;
};

int main() {
    A a = {0};
}

Очевидно, вы не можете установить std::string в ноль. Может ли кто-нибудь дать объяснение (с ссылками на стандарт С++, пожалуйста) о том, что на самом деле должно произойти здесь? А затем объясните, например):

int main() {
    A a = {42};
}

Являются ли эти из них четкими?

Еще раз смущающий вопрос для меня - я всегда даю конструкторы своих структур, поэтому проблема никогда не возникала раньше.

4b9b3361

Ответ 1

Ваша структура является совокупностью, поэтому для нее работают обычные правила для агрегатной инициализации. Процесс описан в 8.5.1. В принципе, для этого посвящено всего 8.5.1, поэтому я не вижу причины копировать все это здесь. Общая идея практически такая же, как и в C, только что адаптированная к С++: вы берете инициализатор справа, вы берете член слева и инициализируете элемент с помощью этого инициализатора. Согласно 8.5/12, это будет инициализация копирования.

Когда вы делаете

A a = { 0 };

вы в основном копируете-инициализацию a.s с 0, то есть для a.s она семантически эквивалентна

string s = 0;

Вышеприведенные компиляции, потому что std::string можно конвертировать из указателя const char *. (И это поведение undefined, поскольку нулевой указатель в этом случае не является допустимым аргументом.)

Ваша версия 42 не будет компилироваться по той же самой причине

string s = 42;

не будет компилироваться. 42 не является константой нулевого указателя, а std::string не имеет средств для преобразования из типа int.

P.S. На всякий случай: обратите внимание, что определение агрегата в С++ не является рекурсивным (в отличие от определения POD, например). std::string не является агрегатом, но он ничего не меняет для вашего A. A по-прежнему является совокупностью.

Ответ 2

8.5.1/12 "Агрегаты" говорит:

Все неявные преобразования типов (раздел 4) рассматриваются при инициализации элемента агрегата с инициализатором из списка инициализаторов.

So

A a = {0};

будет инициализирован с помощью NULL char* (как указано AndreyT и Йоханнес) и

A a = {42};

завершится с ошибкой во время компиляции, поскольку нет неявного преобразования, которое будет соответствовать конструктору std::string.

Ответ 3

0 - константа нулевого указателя

S.4.9:

Константа нулевого указателя является интегральным постоянным выражением (5.19) rvalue целочисленного типа, которое оценивается нуль.

Константа нулевого указателя может быть преобразована в любой другой тип указателя:

S.4.9:

Константа нулевого указателя может быть преобразована в тип указателя; результатом является значение нулевого указателя этого Тип

То, что вы дали для определения A, считается агрегатом:

S.8.5.1:

Агрегат - это массив или класс без конструкторов, не объявленных пользователем, без личных или защищенных нестатические элементы данных, базовые классы и виртуальные функции.

Вы указываете предложение инициализации:

S.8.5.1:

Когда агрегат инициализируется, инициализатор может содержать предложение инициализатора, состоящее из прилагаемой фигурной скобки, разделенный запятыми список инициализатор-предложений для членов агрегата

A содержит член агрегата типа std::string, и к нему применяется предложение инициализатора.

Ваш агрегат инициализируется с копией

Когда агрегат (будь то класс или массив) содержит членов типа класса и инициализируется скобкой, заключенной в initializer-list, каждый такой элемент инициализируется с копией.

Копирование инициализации означает, что у вас есть эквивалент std::string s = 0 или std::string s = 42;

S.8.5-12

Инициализация, возникающая при передаче аргументов, возврат функции, исключение исключения (15.1), обработка исключение (15.3) и список инициализаторов, заключенных в скобки (8.5.1), называются копией-инициализацией и эквивалентны к виду T x = a;

std::string s = 42 не будет компилироваться, потому что нет неявного преобразования, std::string s = 0 будет компилироваться (поскольку существует неявное преобразование), но приводит к поведению undefined.

std::string конструктор для const char* не определяется как explicit, что означает, что вы можете сделать это: std::string s = 0

Чтобы показать, что на самом деле происходит копирование, вы можете выполнить этот простой тест:

class mystring
{
public:

  explicit mystring(const char* p){}
};

struct A {
  mystring s;
};


int main()
{
    //Won't compile because no implicit conversion exists from const char*
    //But simply take off explicit above and everything compiles fine.
    A a = {0};
    return 0;
}

Ответ 4

Как отмечали люди, это "работает", потому что строка имеет конструктор, который может принимать 0 в качестве параметра. Если мы скажем:

#include <map>
using namespace std;

struct A {
    map <int,int> m;
};

int main() {
    A a = {0};
}

то мы получаем ошибку компиляции, так как класс карты не имеет такого конструктора.

Ответ 5

В 21.3.1/9 стандарт запрещает аргумент char* соответствующего конструктора std::basic_string быть нулевым указателем. Это должно вызывать std::logic_error, но я еще не видел, где в стандарте есть гарантия того, что нарушение предусловия вызывает std::logic_error.