Conflict Between Copy Constructor and Forwarding Constructor

Conflict between perfect forwarding constructor and copy constructor in class hierarchy

Try changing Test(const Test& other) : TestBase(other) {} to Test(const Test& other) : TestBase(static_cast<TestBase const&>(other)) {}

The 2nd Test constructor is calling TestBase, and there are two possibilities. One of them takes anything, the other takes a TestBase. But you are passing a Test to it -- the "anything" matches better. By explicitly casting to a TestBase const&, we should be able to get the right one to match.

Another possibility might involve how Test is constructed -- maybe what you passed in matched the template constructor to Test instead? We can test this other possibility by removing the template constructor from Test and seeing if the error goes away.

If that is the case, why wouldn't the technique you linked (to disable the Test template constructor when the type deduced matches Test) work?

Conflict between copy constructor and a template constructor

Tag Dispatching is a used idiom to give a function a distinct signature, similar to your "dummy variable" idea.

struct UseSpecialConstructor{};

template <typename ...T>
explicit C(UseSpecialConstructor, const T&...) { /* implementation */ };

When inlined, compilers are good at optimizing away this unused empty struct parameter, and calling code gains readability from your explicit tag name.

C c1;
C c2{ C::UseSpecialConsructor{}, c1 };

Perfect forwarding and constructors

I would expect the trace object to be created directly in place,
without having to call the move ctor.

I don't know why you expect that. Forwarding does exactly this: moves or copies 1). In your example you create a temporary with trace() and then the forwarding moves it into x

If you want to construct a T object in place then you need to pass the arguments to the construction of T, not an T object to be moved or copied.

Create an in place constructor:

template <class... Args>
wrapper(std::in_place_t, Args&&... args)
:x{std::forward<Args>(args)...}
{}

And then call it like this:

wrapper<trace> w_3 {std::in_place};
// or if you need to construct an `trace` object with arguments;
wrapper<trace> w_3 {std::in_place, a1, a2, a3};

Addressing a comment from the OP on another answer:

@bolov Lets forget perfect forwarding for a minute. I think the
problem is that I want an object to be constructed at its final
destination. Now if it is not in the constructor, it is now garanteed
to happen with the garanteed copy/move elision (here move and copy are
almost the same). What I don't understand is why this would not be
possible in the constructor. My test case proves it does not happen
according to the current standard, but I don't think this should be
impossible to specify by the standard and implement by compilers. What
do I miss that is so special about the ctor?

There is absolutely nothing special about a ctor in this regard. You can see the exact same behavior with a simple free function:

template <class T>
auto simple_function(T&& a)
{
X x = std::forward<T>(a);
// ^ guaranteed copy or move (depending on what kind of argument is provided
}

auto test()
{
simple_function(X{});
}

The above example is similar with your OP. You can see simple_function as analog to your wrapper constructor and my local x variable as analog to your data member in wrapper. The mechanism is the same in this regard.

In order to understand why you can't construct the object directly in the local scope of simple_function (or as data member in your wrapper object in your case) you need to understand how guaranteed copy elision works in C++17 I recommend this excelent answer.

To sum up that answer: basically a prvalue expression doesn't materializez an object, instead it is something that can initialize an object. You hold on to the expression for as long as possible before you use it to initialize an object (thus avoiding some copy/moves). Refer to the linked answer for a more in-depth yet friendly explanation.

The moment your expression is used to initialize the parameter of simple_foo (or the parameter of your constructor) you are forced to materialize an object and lose your expression. From now on you don't have the original prvalue expression anymore, you have a created materialized object. And this object now needs to be moved into your final destination - my local x (or your data member x).

If we modify my example a bit we can see guaranteed copy elision at work:

auto simple_function(X a)
{
X x = a;
X x2 = std::move(a);
}

auto test()
{
simple_function(X{});
}

Without elision things would go like this:

  • X{} creates a temporary object as argument for simple_function. Lets call it Temp1
  • Temp1 is now moved (because it is a prvalue) into the parameter a of simple_function
  • a is copied (because a is an lvalue) into x
  • a is moved (because std::move casts a to an xvalue) to x2

Now with C++17 guaranteed copy elision

  • X{} no longer materializez an object on the spot. Instead the expression is held onto.
  • the parameter a of simple_function can now by initialized from the X{} expression. No copy or move involved nor required.

The rest is now the same:

  • a is copied into x1
  • a is moved into x2

What you need to understand: once you have named something, that something must exist. The surprisingly simple reason for that is that once you have a name for something you can reference it multiple times. See my answer on this other question. You have named the parameter of wrapper::wrapper. I have named the parameter of simple_function. That is the moment you lose your prvalue expression to initialize that named object.


If you want to use the C++17 guaranteed copy elision and you don't like the in-place method you need to avoid naming things :) You can do that with a lambda. The idiom I see most often, including in the standard, is the in-place way. Since I haven't seen the lambda way in the wild, I don't know if I would recommend it. Here it is anyway:

template<class T> class wrapper {
public:

template <class F>
wrapper(F initializer)
: x{initializer()}
{}

private:
T x;
};

auto test()
{
wrapper<X> w = [] { return X{};};
}

In C++17 this grantees no copies and/or moves and that it works even if X has deleted copy constructors and move constructors. The object will be constructed at it's final destination, just like you want.


1) I am talking about the forwarding idiom, when used properly. std::forward is just a cast.

Why is forwarding reference constructor called instead of copy constructor?

I am guessing that it's because the move constructor was implicitly defined but not the copy constructor.

No, both are implicitly defined for class Something.

Why is the copy constructor being resolved to the templated forwarding reference constructor

Because the copy constructor takes const Something& as its parameter. That means for the copy constructor to be called, implicit conversion is needed for adding const qualifier. But the forwarding reference constructor could be instantiated to take Something& as its parameter, then it's an exact match and wins in the overload resolution.

So if you make something const, the implicitly defined copy constructor will be invoked for the 3rd case instead of the forwarding reference constructor.

LIVE

but not the move constructor?

Because for the move constructor the above issue doesn't matter. For the invocation of the 1st and 2nd case, both implicitly defined move constructor and forwarding reference constructor are exact match, then non-template move constructor wins.

std::forward and copy constructor

Func(mc);

This call will deduce T to be MyClass&. Note the reference. This is due to the way perfect forwarding has been introduced into C++: A forwarding reference such as the T&& function parameter of Func will be deduced to be an lvalue reference if the corresponding function argument is an lvalue-expression; otherwise (for xvalues and prvalues) it will not be deduced to be a reference type.

The type deduced for T will then be reference-collapsed with the && from the function parameter T&& t to form the final function parameter type. In the case Func(mc), T == MyClass&, so the function parameter becomes MyClass& &&, collapsed to MyClass&.

Since T == MyClass&, the declaration of the local variable new_t_ will declare a reference in this case. No new object will be created:

template <>
void Func<MyClass&>(MyClass& t) {
MyClass& new_t_(std::forward<MyClass&>(t));
new_t_.val *= 2;
}

Func(MyClass());

Here, the function argument is the prvalue-expression MyClass(). T will be deduced to MyClass (without reference). The function template instantiation produced looks like this:

template <>
void Func<MyClass>(MyClass&& t) {
MyClass new_t_(std::forward<MyClass>(t));
new_t_.val *= 2;
}

If you want to copy/move the function argument into a local variable, the std::decay metafunction can be used - or, more specific to the OP's problem, the std::remove_reference metafunction. E.g.

template <typename T>
void Func(T&& t) {
using DT = typename std::decay<T>::type;
DT new_t_(std::forward<T>(t));
new_t_.val *= 2;
}

Why does std::pair have two different constructors for both const reference and forwarding reference parameter?

In which case does first constructor win in overload resolution?

It wins when being passed const lvalues with the exact same type with members of the std::pair, i.e. const T1 and const T2. Both the constructors are exact match and non-template one would win. E.g.

const int i = 0;
const int j = 0;
std::pair<int, int> p(i, j);

The constructor taking forwarding references was added since C++11, I think the 1st one is reserved just for consistency.

rationale behind c++ implicit copy and move constructor?

This issue was discussed on the MSVC++ bugstracker page some years ago (so if it is not fixed yet, it is a known issue on MSVC++). The Standard says

  • ... the base or member is direct-initialized with the corresponding base or member of x.

I did test various compilers when I read the bugreport, and all of them "magically casted". The Standard appears to be quiet on the very details (also about the value category and c/v qualifiers), and just says "with the corresponding base ... of x", which IMO makes much more sense if you take it to mean "with not x, but with the corresponding base ... of x" rather than that you pass it the complete object (I would even go as far as saying that it can only make sense that way).



Related Topics



Leave a reply



Submit