Почему это не скомпилируется без конструктора по умолчанию? - PullRequest
0 голосов
/ 17 декабря 2018

Я могу сделать это:

#include <iostream>

int counter;

int main()
{
    struct Boo
    {
        Boo(int num)
        {
            ++counter;
            if (rand() % num < 7) Boo(8);
        }
    };

    Boo(8);

    return 0;
}

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

#include <iostream>

int counter;

int main()
{
    struct Boo
    {
        Boo(int num)
        {
            ++counter;
            if (rand() % num < 7) Boo(num); // No default constructor 
                                            // exists for Boo
        }
    };

    Boo(8);

    return 0;
}

Как вызывается конструктор по умолчанию во втором примере, но не в первомпример?Это ошибка, которую я получаю в Visual Studio 2017.

В онлайн-компиляторе C ++ onlineGDB я получаю ошибки:

error: no matching function for call to ‘main()::Boo::Boo()’
    if (rand() % num < 7) Boo(num);

                           ^
note:   candidate expects 1 argument, 0 provided

Ответы [ 3 ]

0 голосов
/ 17 декабря 2018

Clang выдает это предупреждающее сообщение:

<source>:12:16: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]
            Boo(num); // No default constructor 
               ^~~~~

Это самая неприятная проблема с анализом.Поскольку Boo является именем типа класса, а num не является именем типа, Boo(num); может быть либо конструкцией временного типа Boo с num аргументом для конструктора Boo.или это может быть объявление Boo num; с дополнительными скобками вокруг объявления num (которое всегда может иметь объявление).Если оба являются допустимыми интерпретациями, стандарт требует, чтобы компилятор принял объявление.

Если он анализируется как объявление, тогда Boo num; вызовет конструктор по умолчанию (конструктор без аргументов), который не объявленлибо вами, либо неявно (потому что вы объявили другой конструктор).Следовательно, программа некорректна.

Это не проблема с Boo(8);, поскольку 8 не может быть идентификатором переменной (идентификатор-объявления), поэтому она анализируется как вызов, создающий Boo временно с 8 в качестве аргумента для конструктора, тем самым не вызывая конструктор по умолчанию (который не объявлен), а тот, который вы определили вручную.

Вы можете устранить неоднозначность из объявления, используя Boo{num}; вместо Boo(num); (потому что {} вокруг декларатора недопустимо), сделав временную именованную переменную, например, Boo temp(num);, или поместив ее в качестве операнда в другое выражение, например, (Boo(num));, (void)Boo(num); и т. д.

Обратите внимание, что объявление было бы правильно сформировано, если бы можно было использовать конструктор по умолчанию, поскольку оно находится внутри области блока ветвления if, а не области блока функции, и просто затеняетnum в списке параметров функции.

В любом случае нецелесообразно неправильно использовать создание временных объектов для чего-то, чтодолжен быть нормальным вызовом функции (члена).

Этот конкретный тип наиболее неприятного анализа с одним нетиповым именем в скобках может произойти только потому, что он предназначен для создания временного объекта и немедленного его удаления илиальтернативно, если целью является создание временного объекта, используемого непосредственно в качестве инициализатора, например, Boo boo(Boo(num)); (фактически объявляется функция boo, принимающая параметр с именем num с типом Boo и возвращающая Boo).

Немедленно отбрасывать временные значения обычно не предполагается, и можно избежать случая инициализации, используя инициализацию скобок или двойные парантезы (Boo boo{Boo(num)}, Boo boo(Boo{num}) или Boo boo((Boo(num)));, но не Boo boo(Boo((num)));).

ЕслиBoo не было именем типа, оно не могло быть объявлением и не возникало никаких проблем.

Я также хочу подчеркнуть, что Boo(8); создает новый временный объект типа Boo даже внутриобласть действия класса и определение конструктора.Это, как можно ошибочно подумать, не вызов конструктора с указателем this вызывающей стороны, как для обычных нестатических функций-членов.Невозможно вызвать другой конструктор таким образом внутри тела конструктора.Это возможно только в списке инициализатора члена конструктора.


Это происходит, даже если объявление будет некорректно сформировано из-за отсутствия конструктора из-за [stmt.ambig] / 3:

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

[...]

Устранение неоднозначности предшествует синтаксическому анализу, и утверждение, неоднозначное как декларация, может быть неправильно сформированной декларацией.


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

0 голосов
/ 05 января 2019

Вот предупреждение clang

truct_init.cpp: 11: 11: ошибка: переопределение 'num' с другим типом: 'Boo' против 'int'

0 голосов
/ 17 декабря 2018

Это известно как самый неприятный анализ (термин использовался Скоттом Мейерсом в Effective STL) .

Boo(num) не вызывает конструктор и не создает временный.Clang дает хорошее предупреждение, чтобы увидеть (даже с правильным именем W vexing-parse ):

<source>:12:38: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]

Так что то, что видит компилятор, эквивалентно

Boo num;

который является переменной decleration.Вы объявили переменную Boo с именем num, для которой нужен конструктор по умолчанию, даже если вы хотели создать временный объект Boo.Стандарт c ++ требует, чтобы компилятор в вашем случае предполагал, что это объявление переменной.Теперь вы можете сказать: «Эй, num - это int, не делай этого».Тем не менее, стандарт гласит: :

Устранение неоднозначности является чисто синтаксическим;то есть значение имен, встречающихся в таком утверждении, вне зависимости от того, являются ли они именами типов или нет, обычно не используется в неоднозначности или не изменяется.Шаблоны классов создаются по мере необходимости, чтобы определить, является ли квалифицированное имя именем типа.Устранение неоднозначности предшествует синтаксическому анализу, и утверждение, неоднозначное как декларация, может быть неправильно сформированной декларациейЕсли во время синтаксического анализа имя в параметре шаблона связывается не так, как оно было бы во время пробного анализа, программа некорректна.Диагностика не требуется.[Примечание: это может произойти, только если имя объявлено ранее в объявлении.- конец примечания]

Так что выхода из этого нет.

Для Boo(8) этого не может быть, так как анализатор может быть уверен, что это не decleration (8не является допустимым именем идентификатора) и вызывает конструктор Boo(int).

Кстати: Вы можете устранить неоднозначность, используя круглые скобки:

 if (rand() % num < 7)  (Boo(num));

или в моеммнение лучше, используйте новый унифицированный синтаксис инициализации

if (rand() % num < 7)  Boo{num};

, который затем скомпилирует см. здесь и здесь .

...