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:

struct Wrapper { Wrapper(Wrapper const&) { cout << "copy"; } Wrapper(Wrapper &&) { cout << "move"; } template <typename T> Wrapper(T&&) { cout << "forward"; } };

We have a normal copy constructor, a normal move constructor and a perfect-forwarding constructor. Probably a typical situation for a wrapper class. This T&& is exactly what Scott Meyers calls a universal reference. Now imagine the code that uses our type:

Wrapper w = 1; // forwarding constructor Wrapper v = w; // which 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:

auto container::begin() -> iterator; auto container::begin() const -> const_iterator;

We could think about fixing the problem by declaring an another non- const constructor:

Wrapper::Wrapper(Wrapper const&); Wrapper::Wrapper(Wrapper&); Wrapper::Wrapper(Wrapper&&);

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:

template <typename T, typename U> constexpr inline bool NonSelf() { using DecayedT = typename decay<T>::type; // no ref, no const return !is_same<DecayedT, U>::value; }

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:

template <typename T, typename U> constexpr inline bool NonSelf() { using DecayedT = typename decay<T>::type; return !is_same<DecayedT, U>::value && !is_base_of<U, DecayedT>::value; }

We can use our meta function like this:

NonSelf<T, Wrapper>();

Now, how do we use our predicate to constrain a perfect forwarding function? If we had Concepts Lite in the language, we would be able to express it like this:

struct Wrapper { Wrapper(Wrapper const&) { cout << "copy"; } Wrapper(Wrapper &&) { cout << "move"; } template <typename T> requires NonSelf<T, Wrapper>() Wrapper(T&&) { cout << "forward"; } };

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:

#define ENABLE_IF(...) \ typename std::enable_if<__VA_ARGS__>::type* = nullptr

Equipped with the macro, we can fix our initial example as follows:

struct Wrapper { Wrapper(Wrapper const&) { cout << "copy"; } Wrapper(Wrapper &&) { cout << "move"; } template <typename T, ENABLE_IF(NonSelf<T, Wrapper>())> Wrapper(T&&) { cout << "forward"; } };

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:

template <typename T> struct optional { template <typename U> optional& operator=(U&&); // ... };

But since we naturally want a special case for copy- and move-assignment:

template <typename T> struct optional { optional& operator=(optional const&); optional& operator=(optional &&); template <typename U> optional& operator=(U&&); // ... };

And we arrive at the same problem:

optional<int> ob, oa; ob = oa; // NOT a copy-assignment!

We already know how to fix it:

template <typename T> struct optional { optional& operator=(optional const&); optional& operator=(optional &&); template <typename U, ENABLE_IF(NonSelf<U, optional<T>>())> optional& operator=(U&&); // ... };

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?

Let me show you how you will be able to do it in C++14, with a variable template:

template <typename T> struct optional; // forward decl template <typename U, typename T> constexpr bool NonSelfAndConverts = std::is_convertible<U&&, T>::value && std::is_assignable<T&, U>::value && NonSelf<U, optional<T>>();

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:

template <typename T> struct optional { optional& operator=(optional const&); optional& operator=(optional &&); template <typename U, ENABLE_IF(NonSelfAndConverts<U, T>)> optional& operator=(U&&); // ... };

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

References