For the following program:
#include <iostream>
struct Foo
{
Foo() { std::cout << "Foo()\n"; }
Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; }
~Foo() { std::cout << "~Foo()\n"; }
};
struct A
{
A(Foo) {}
};
struct B : A
{
using A::A;
};
int main()
{
Foo f;
B b(f);
}
GCC gives:
$ g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
Foo()
Foo(const Foo&)
~Foo()
~Foo()
VS 2017 (also in C++17 mode) gives:
Foo()
Foo(const Foo&)
Foo(const Foo&)
~Foo()
~Foo()
~Foo()
Who's right, and why?
(Let's also not forget that VS 2017 doesn't do mandated copy elision properly. So it could just be that the copy is "real" but GCC elides it per C++17 rules where VS doesn't...)
It appears that Visual Studio doesn't implement P0136 yet. The correct C++17 behavior is a single copy, the original C++14 behavior was two copies.
The C++14 rules (N4140:[class.inhctor]) would interpret:
struct B : A
{
using A::A;
};
as:
struct B : A
{
B(Foo f) : A(f) { }
};
The introduced constructors are speciifed in p3, the mem-initializer equivalence in p8. Hence you get two copies of Foo
: one into B
's synthesized constructor and one into A
's real constructor.
The C++17 rules, as a result of P0136, are very different (N4659:[class.inhtor.init]): there, we directly invoke A
's constructor. It's not like we're adding a new constructor to B
anymore - and it's not a mechanism that's otherwise expressible in the language. And because we're directly invoking A(Foo)
, that's just the one single copy instead of two.
Elision notwithstanding, it looks to me like Visual Studio is wrong:
[C++17: class.inhctor.init]/1:
When a constructor for typeB
is invoked to initialize an object of a different typeD
(that is, when the constructor was inherited ([namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize theD
object and each base class subobject from which the constructor was inherited, except that theB
subobject is initialized by the invocation of the inherited constructor. The complete initialization is considered to be a single function call; in particular, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of theD
object.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With