Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to initialize a class with constructed class without using std::move?

Tags:

c++

// Example program
#include <iostream>
#include <string>
#include <utility>

class A {
public:
    int x; 
};

class B {
public:
    B(A &&a) : m_a(std::move(a)) {}
    A m_a;
};

int main()
{
    B var(std::move(A()));
    // B var(A()); // does not compile why?

    std::cout << var.m_a.x << "\n";

}

In the above snippet the commented out line doesn't compile. The error message appears that it's treating var like a function declaration. Even if A has a parameter for constructor it's still treated like a function declaration. Is there a way to write it so it won't be treated like function declaration? Using typename doesn't help in this case.

like image 822
over_optimistic Avatar asked Dec 11 '22 13:12

over_optimistic


2 Answers

This problem is known as the most vexing parse, which exactly describes your situation, where the parser doesn't know if you want a function declaration, or the instantiation of an object. It's ambiguous in the sense that, to a human, some given piece of code does X, but to the compiler it's clearly doing Y.

One way of getting around it is to use the list initialization syntax:

B var{A()}; // note the use of brackets

This is no longer ambiguous, and it will call the constructor as you wanted. You also may use it in A, or in both:

B var(A{});
B var{A{}};

Now, why is it ambiguous? Take for instance the declaration of a function parameter that is a pointer to function:

int foo(int (*bar)());

Here, the parameter is of type pointer to function that takes no arguments and has return type of int. Another way to declare pointers to function is by omitting the parentheses in the declarator:

int foo(int bar());

Which still declares a pointer to function identical to the previous one. As we're in the context of declaring parameters (parameter-declaration-clause), the grammar being parsed is a type-id, which is partly built on top of abstract-declarator. Therefore, this allows us to remove the identifier:

int foo(int());

And we still end up with the same type.

With that all said, let's examine your code and compare it to the examples above:

B var(A());

We have something that looks like a variable declaration of type B being initialized with A(). So far, so good. But wait, you said this doesn't compile!

The error message appears that it's treating var like a function declaration.

var is in fact a function declaration, even though to you it didn't look like that in the first place. This behavior is due to [dcl.ambig.res]/1:

[…] the resolution is to consider any construct that could possibly be a declaration a declaration.

And that sentence applies here. Looking back at the previous examples:

int foo(int());

That is just as ambiguous as your code: foo could possibly be a declaration, so the resolution is to interpret this as one. Your B var(A()) could possibly be a declaration just as well, so it holds the same resolution.

The standard has a few examples of these cases on [dcl.ambig.res]/1, example #1, and also gives some tips on how to disambiguate them on [dcl.ambig.res]/1, note #1:

[ Note: A declaration can be explicitly disambiguated by adding parentheses around the argument. The ambiguity can be avoided by use of copy-initialization or list-initialization syntax, or by use of a non-function-style cast. — end note  ]

[ Example:

struct S {
  S(int);
};

void foo(double a) {
 S w(int(a));                  // function declaration
 S x(int());                   // function declaration
 S y((int(a)));                // object declaration
 S y((int)a);                  // object declaration
 S z = int(a);                 // object declaration
}

— end example  ]

like image 134
Mário Feroldi Avatar answered Dec 13 '22 02:12

Mário Feroldi


Instead construct your A like so

B var(A{});

and it will not be confused for a function declaration.

like image 41
Paul Rooney Avatar answered Dec 13 '22 02:12

Paul Rooney