Update. One of the readers informed me that the same problem has been already described by R. Martinho Fernandes (see here), Eric Niebler (see here) and Scott Meyers (see here); so I should at least link to their articles. Now I also mention, following Eric Niebler’s article, that classes derived from our type should be treated specially.

I was struck by this problem the second time now, so I guess it deserves a post in this blog. Imagine the following type with a perfect-forwarding constructor:

Guess what constructor is called in the second line? We clearly want to make a copy; but it is not the copy constructor that will be called. Check it out. Do you know why?

The answer

A copy constructor is of course a viable candidate for the initialization in the second line. But compiler needs to check if the constructor template cannot be used to create a constructor that is an even better match than a copy constructor. And lo, it can!

It is able to generate a constructor with signature:

Wrapper(Wrapper&);

Quite obvious, isn’t it? After all w is a non-const lvalue. Note that we would have the same problem if instead of defining our copy- and move-constructor ourselves we relied on the compiler-generated ones.

I must admit that this observation conflicts with my intuitive understanding of what copy constructor is. I used to think that copy constructor is the constructor that will be called when we request for a copy of a given object. Now, with the addition of perfect forwarding, this intuition ceased to be valid. When we declare a set of constructors (or function overloads, in general) like in our class Wrapper we believe we say: perfect-forward anything except when T is a Wrapper, because for this case we have specialized constructors. But apparently, by providing only two specialized constructors, we did not exhaust all possible combinations of references and const/volatile qualifiers.

Fixing the problem

Perhaps this is a problem in the language, that you cannot specify a copy constructor that will be copying from lvalue references (const or not) and will guarantee that the original is not touched. It looks like in our case the fact that T and const T are distinct types, on which you can overload functions, is an obstacle. On the other hand, const-based overloading is very useful in the case of containers:

And forward the initialization in the non-const to the const one. While this would solve the case above, it is somewhat counter-intuitive, and misleading. We did not cover other combinations of references, const and volatile, and we are sending the wrong message, that we would do something special in the new constructor and that we do not guarantee that the state of the original would be untouched.

We can do something different though. We can make the perfect-forwarding constructor less perfect, by declaring that it should not work for type Wrapper and its variations. “Not work” means that it would not be a viable candidate during the task of picking the most appropriate overload. What we mean by “not for Wrapper and its variations” needs to be precisely defined by a compile-time predicate:

NonSelf is a template for generating constexpr functions. We operate only on template parameter types. First, we use a meta-transformation std::decay: it takes a type, and returns another type, which results from removing top-level references and top-level const/volatile qualifiers from the argument. DecayedT is now “decayed”. Next we compare if they are not same types.

Now, this is a bit simplified version of what our predicate should really look like. We should also consider the case where T is a class type derived from Wrapper. In that case we probably also want to ‘disable’ the perfect forwarding constructor. If so, Our predicate would probably read:

It reads, “don’t even consider this template unless the boolean predicate is satisfied.”

But we do not have Concepts Lite in the Standard C++ yet. It is very likely that we will get them as a “Technical Specification” still in 2014 (see this status page). This is not a standard yet, but hopefully a sufficient incentive for compiler vendors to implement the feature. Until then, we have to implement a similar solution with tools at hand. In fact, we have one: SFINAE. It is not as elegant and simple as Concepts Lite, but it should do. We will use a Standard Library component built atop SFINAE: std::enable_if. You can find an introduction to enable_if in Boost documentation, here. In addition, we will utilize a new way of using enable_if in C++11 described by Matt Calabrese, here.

In order to hide the necessary boilerplate template-related code, we are going to introduce a macro. I am not sure if this is a good idea in general, but for brevity of the code examples, I decided to go with a macro:

I will not attempt to describe all the magic involved in making this code work. In short, we are messing with the second template parameter, which is not a type parameter, but a pointer. It has a default value, so for the user the second parameter is not visible, expect that it messes with the overload resolution logic. If you want to learn more, follow the links provided above. If you consider this outrageous or frightening, that one should be forced to use tricks like this, you are not alone. This is one of the reasons why Concepts Lite are being developed.

Not only constructors…

You could argue that it is not a good idea to make the perfect-forwarding constructor implicit. In fact, if we made it explicit, we would fix our problem without “enable if” tricks. This is how std::tuple addresses this problem. But I have only used constructors for demonstration. A similar problem appears to any other function. Consider assignment operator in optional similar Boost.Optional. We would like the following expressions to work:

optional<unique_ptr<Base>> ob;
ob = unique_ptr<Derived>{};

Note that we are assigning to optional<T> something that is not T but a type convertible to T. In order to facilitate this, we need to provide the following assignment operator:

Now that we know how to conditionally enable/disable templates from overload resolution, we can take it further. We do not want to assign optional<T> with any U. Only when T is assignable and convertible from U. do you know how to change our definition of the almost-perfect-forwarding assignment?

It is not a function template, nor a class template, nor a C++11 alias template. It is a variable template: a parametrized variable. The usage is even simpler than that of a constexpr function, as it does not require typing the empty parentheses:

Summary

The lesson I learned from playing with the perfect-forwarding, is that you have to be careful not to make functions too perfect-forwarding in cases where you have other overloads. This guideline is followed, for instance, in the proposal for Fundamentals Technical Specification: std::experimental::any. Note how it uses phrase “This constructor/operator shall not participate in overload resolution if decay<ValueType>::type is the same type as std::any.”

Thanks for the links Seth. In fact I didn’t see them before. It looks like I am repeating something others have already discovered. I should read more I guess.

Regarding is_base_of, my only practical encounter with this forwarding problem was with std::experimental::optional and in this case is_base_of doesn’t change anything. But maybe because it is a value-semantic type (they are not easily mixed with OO-style polymorphism). So in the general case: I do not have an opinion.

On the other hand, after receiving the same remark from Eric, and having given it a bit thought, I think is_base_of should be included in the predicate. While in some cases it doesn’t matter, it may help with other. Of course there might be exceptional cases where it would disturb, I guess.

Correction. Now I realize that is_base_ofis needed, even in the case of optional. Argh! I have been trying to learn C++ for so many years and I still can’t see these things. Thanks for pointing it out.

My opinion is that stripping cv-qualifiers like in Different is flawed and that the real bug is optional not providing both op=(optional &) and op=(optional const &) to begin with.

Consider what happens if you do optional. If the auto_ptr is a member then you really do need op=(optional&). It’s true, auto_ptr is horrible and deprecated, but there’s really nothing stopping people having different assignment and ‘copy’ construction semantics for const and non-const lvalue references even in C++11…so wrapper classes perhaps shouldn’t assume they’re the same :-|

What about not passing at all by reference and instead passing by value? The general rule I’ve been hearing is if you are going to make a copy anyway, pass by value and let copy elision handle it. How does that impact this discussion.
I stumbled on your blog in a google search. Nice job. I look forward to a lot of reading.

Copy constructor and move constructor are one place, where you cannot apply this rule. Copy elision (and in fact “move elision” would be a more accurate name) only works in places where move constructor would work if compiler was not eliding. In other words, if compiler cannot move-construct an object, it is not allowed to elide: so you have to provide a move (and probably also a copy-) constructor: using a reference argument. Defining constructor like Wrapper(Wrapper w) would be an infinite recursion.

The article shows an example of defining an assignment. But what it says is “if you need to make a copy inside your function anyway, only then should you consider moving this copying into function argument”. It would not apply to optional because as you can see in the reference implementation here, I do not need to make a copy inside the body.

I also found one problem with this suggestion (of passing by value in assignment operator). I do not know how to define the noexcept specification for such assignment. See this comment for details.