IT TIP

왜 이것이 기본 생성자없이 컴파일되지 않습니까?

itqueen 2020. 10. 29. 20:16
반응형

왜 이것이 기본 생성자없이 컴파일되지 않습니까?


나는 이것을 할 수있다 :

#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

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);유형의 임시의 건설이 될 수 Boonum에 인수되는 Boo'생성자 또는이 선언 될 수 Boo num;선언자의 주위에 여분의 괄호 num선언자 항상있을 수 있습니다 ( ). 둘 다 유효한 해석이면 표준에 따라 컴파일러가 선언을 가정해야합니다.

선언으로 파싱 Boo num;되면 기본 생성자 (인수가없는 생성자)를 호출합니다.이 생성자는 사용자가 선언하거나 암시 적으로 (다른 생성자를 선언했기 때문에) 선언되지 않습니다. 따라서 프로그램의 형식이 잘못되었습니다.

This is not an issue with Boo(8);, because 8 cannot be a variable's identifier (declarator-id), so it is parsed as a call creating a Boo temporary with 8 as argument to the constructor, thereby not calling the default constructor (which is not declared), but the one you defined manually.

You can disambiguate this from a declaration by either using Boo{num}; instead of Boo(num); (because {} around the declarator is not allowed), by making the temporary a named variable, e.g. Boo temp(num);, or by putting it as an operand in another expression, e.g. (Boo(num));, (void)Boo(num);, etc.

Note that the declaration would be well-formed if the default constructor was usable, because it is inside the if's branch block scope rather than the function's block scope and would simply shadow the num in the function's parameter list.

In any case it doesn't seem a good idea to misuse temporary object creation for something that should be a normal (member) function call.

This particular type of most-vexing parse with a single non-type name in the parenthesis can only happen because the intend is to create a temporary and immediately discard it or alternatively if the intend is to create a temporary used directly as an initializer, e.g. Boo boo(Boo(num)); (actually declares function boo taking a parameter named num with type Boo and returning Boo).

Discarding temporaries immediately is usually not intended and the initializer case can be avoided using brace-initialization or double-parantheses (Boo boo{Boo(num)}, Boo boo(Boo{num}) or Boo boo((Boo(num)));, but not Boo boo(Boo((num)));).

If Boo wasn't a type name, it could not be a declaration and no problem occurs.

I also want to emphasize that Boo(8); is creating a new temporary of type Boo, even inside the class scope and constructor definition. It is not, as one might erroneously think, a call to the constructor with the caller's this pointer like for usual non-static member functions. It is not possible to call another constructor in this way inside the constructor body. This is only possible in the member initializer list of the constructor.


This happens even though the declaration would be ill-formed due to missing constructor, because of [stmt.ambig]/3:

The disambiguation is purely syntactic; that is, the meaning of the names occurring in such a statement, beyond whether they are type-names or not, is not generally used in or changed by the disambiguation.

[...]

Disambiguation precedes parsing, and a statement disambiguated as a declaration may be an ill-formed declaration.


Fixed in edit: I overlooked the declaration in question being in a different scope than the function parameter and the declaration therefore being well-formed if the constructor was available. This is not considered during disambiguation in any case. Also expanded on some details.


This is known as the most vexing parse (The term was used by Scott Meyers in Effective STL).

Boo(num) does not invoke the constructor nor does it create a temporary. Clang gives a good warning to see (even with the right name Wvexing-parse):

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

So what the compiler sees is equivalent to

Boo num;

which is a variable decleration. You declared a Boo variable with name num, which needs the default constructor, even though you wanted to create a temporary Boo-object. The c++ standard requires the compiler in your case to assume this is a variable declaration. You might now say: "Hey, num is an int, don't do that." However, the standard says:

The disambiguation is purely syntactic; that is, the meaning of the names occurring in such a statement, beyond whether they are type-names or not, is not generally used in or changed by the disambiguation. Class templates are instantiated as necessary to determine if a qualified name is a type-name. Disambiguation precedes parsing, and a statement disambiguated as a declaration may be an ill-formed declaration. If, during parsing, a name in a template parameter is bound differently than it would be bound during a trial parse, the program is ill-formed. No diagnostic is required. [ Note: This can occur only when the name is declared earlier in the declaration. — end note  ]

So there is no way out of this.

For Boo(8) this cannot happen, as the parser can be sure this is not a decleration (8 is not a valid identifier name) and invokes the constructor Boo(int).

By the way: You can disambiguate by using enclosing parentheses:

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

or in my opinion better, use the new uniform initialization syntax

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

Which will then compile see here and here.


Here is clang warning

truct_init.cpp:11:11: error: redefinition of 'num' with a different type: 'Boo' vs 'int'

참고URL : https://stackoverflow.com/questions/53806896/why-wont-this-compile-without-a-default-constructor

반응형