Take a look at this innocent looking piece of code in C++. There are no templates, no virtual functions or inheritance, but the creators of this beautiful language hid a gift right in front of us.
struct A {
A (int i) {}
};
struct B {
B (A a) {}
};
int main () {
int i = 1;
B b(A(i)); // (1)
return 0;
}
Question: What is the type of variable b? It is not the one you would expect at first glance.
Analysis
Well, the type of variable b is definitely not B. Otherwise, why would I write this article at all? I’m not going to give you the answer right away. Instead, I will tell you how to find it without studying a thousand pages of the standard.
To begin with, let's add some debugging:
#include
struct A {
A (int i) { std::cout << 'A';}
};
struct B {
B (A a) { std::cout << 'B';}
};
int main () {
int i = 1;
B b(A(i)); // (1)
return 0;
}
If we try to run this code, it turns out that nothing is printed at all. But if we replace the first line with
B b(A(1));
Suddenly, everything starts to work..
Now, let's look closely at the compiler output, when warnings are enabled.
$ g++ -W -Wall test.cpp
x.cpp:2: warning: unused parameter ‘i’
x.cpp:6: warning: unused parameter ‘a’
x.cpp: In function ‘int main()’:
x.cpp:10: warning: unused variable ‘i’
As for the first two lines, everything is quite clear. Indeed, parameters of constructors are not used. But the last line looks really strange. How come variable i is not used, while we use it in the next line?
I guess this information could be enough to answer the question after some thinking. But if there are no right thoughts coming to our mind, why not ask the compiler? That’s when RTTI comes to the rescue.
#include
#include
struct A {
A (int i) {}
};
struct B {
B (A a) {}
};
int main () {
int i = 1;
B b(A(i)); // (1)
std::cout << typeid(b).name() << std::endl;
return 0;
}
When compiling with GCC 4.3, the result of this program is the following string:
F1B1AE
It contains the information about the variable type (of course, another compiler will print a different string, the type_info::name() output format is not described in the standard and left here to the discretion of the developer). c++filt will help us to find out what these letters and numbers mean.
$ c++filt -t F1B1AE
B ()(A)
Here’s the answer: it is a function that accepts a parameter of type A and returns a value of type B.
The Reason
It remains to understand why our line is interpreted in such an unexpected way. The thing is that in the declaration of the variable type, the extra parentheses around the name are ignored. For instance, we can write
int (v);
and this will mean exactly the same thing as
int v;
Therefore, we can rewrite the first line without changing its meaning, by removing an extra pair of parentheses:
B b(A i);
We can see now that b is the declaration of a function with one argument of type A, and that this function returns a value of type B.
At the same time, we have explained the strange warning about the unused variable i. And really, it has no relation to the formal parameter i.
Workarounds
It remains to explain the compiler what we actually want from it. That is, to get a variable of type B, initialized by a variable of type A. The easiest way is to add an extra parentheses, like this:
B b((A(i)));
or like this:
B b((A)(i));
This should be enough to convince the parser that it is not a declaration of a function.
As an alternative, we can call a constructor by using an assignment operator, unless the constructor is declared as explicit:
B b = A(i);
Despite the = operator, there’s no extra copy takes place here (as can be easily seen by introducing a private copy constructor in class B).
But we can also introduce an additional variable:
A a(i);
B b(a);
However, this will require an extra copy of variable a, which is acceptable in many cases.
Choose the method that seems more clear to you :)
Inspired by the post on StackOverflow.
2 comments
clang does a good job of detecting vexing parse cases and generating helpful warnings, for example your code would generate the following warning see it live:
In C++11 uniform initialization also offers an alternative:
This is probably one of the older stackoverflow questions covering it but it does not touch on all these points. I cover all these points in a newer related question here.
It's (almost always) true that no extra copy takes place, but it's not true that you can have a private (or deleted since C++11) copy constructor as the standard explicitly requires a copy constructor to be accessible (can't remember the exact wording, but that's the gist of it) even though the compiler is allowed not to call it even if it has side effects.
In other words, the standard allows to avoid calling a copy constructor during a copy-initialization, even if you rely on its side effects (e.g. the copy constructor does I/O), but a copy constructor must nonetheless be eligible for calling, otherwise the copy-initialization statement is ill-formed
Upload image