c++visual-studioc++17inherited-constructors

Should args to inherited constructors be copied when invoking the base ctor or not?


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...)


Solution

  • 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.