13

In the following code, the variadic constructor is called twice. How can I get the copy constructor to be called instead of the single argument version of the variadic constructor when appropriate?

#include <iostream>

struct Foo
{
    Foo(const Foo &)
    {
        std::cout << "copy constructor\n";
    }

    template<typename... Args>
    Foo(Args&&... args)
    {
        std::cout << "variadic constructor\n";
    }

    std::string message;
};

int main()
{
    Foo f1;
    Foo f2(f1); // this calls the variadic constructor, but I want the copy constructor.
}
James McNellis
  • 348,265
  • 75
  • 913
  • 977
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274

2 Answers2

14

This actually has nothing to do with the fact that the constructor is variadic. The following class with a non-variadic constructor template exhibits the same behavior:

struct Foo
{
    Foo() { }

    Foo(const Foo& x)
    {
        std::cout << "copy constructor\n";
    }

    template <typename T>
    Foo(T&& x)
    {
        std::cout << "template constructor\n";
    }

};

The problem is that the constructor template is a better match. To call the copy constructor, a qualification conversion is required to bind the non-const lvalue f1 to const Foo& (the const qualification must be added).

To call the constructor template, no conversions are required: T can be deduced to Foo&, which after reference collapsing (Foo& && -> Foo&), gives the parameter x type Foo&.

You can work around this by providing a second copy constructor that has a non-const lvalue reference parameter Foo&.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 1
    There’s slightly more to it, namely reference collapsing (how was that called?), i.e. `T&&&` => `T&` because otherwise an lvalue (`f1`) couldn’t bind to `T&&`. – Konrad Rudolph Jun 14 '12 at 16:50
6

Just provide an exact-match overload, i.e. one with a non-const Foo&, in addition to the conventional copy constructor. Then you can delegate the call via an explicit cast:

Foo(Foo& other) : Foo{const_cast<Foo const&>(other)} { }
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 1
    Shouldn't const_cast be used? – Fan Aug 24 '18 at 19:35
  • @Fan No, why? Non const to const is an implicit conversion by the C++ rules, we just use an explicit cast (= `static_cast`) to force it here. `const_cast` is only needed to cast *away* constness. – Konrad Rudolph Aug 25 '18 at 10:41
  • According to https://en.cppreference.com/w/cpp/language/const_cast, const_cast "converts between types with different cv-qualification." It is not necessarily from const to non-const. – Fan Aug 27 '18 at 17:58
  • @Fan I didn’t dispute that. I simply said that you don’t *need* it here, `static_cast` works too. But you’re right, there’s an argument to be made for using `const_cast` here, because this prevents accidentally changing the type (which `static_cast` might do if you have a typo in the type). You could probably even use `std::forward` here but that’s abusing its semantics slightly. – Konrad Rudolph Aug 27 '18 at 19:01