Too perfect forwarding

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

  1. R. Martinho Fernandes, “Some pitfalls with forwarding constructors”.
  2. Eric Niebler, “Universal References and the Copy Constructor”.
  3. Scott Meyers, “Copying Constructors in C++11 “
  4. Matthias Vallentin, in Stack Overflow, “(Im)perfect forwarding with variadic templates”.

About these ads
This entry was posted in programming and tagged , , , . Bookmark the permalink.

15 Responses to Too perfect forwarding

  1. Michal Mocny says:

    Andrzej, fantastic article. Wonderful way to present these new features, teaching such a great lesson while at it.

    I do wonder, will we be able to use the Concepts Lite succinct syntax, or at least the shorthand, instead of being explicit about the requires clause, such as:

    Wrapper(Different&&) { cout << "forward"; }
    

    Or

    template<Different T>
    Wrapper(T&&) { cout << "forward"; }
    

    I guess that will depend on how Concepts Lite ends up..

  2. What about following sample

    optional<unique_ptr<Base>> ob_base;
    optional<unique_ptr<Derived>> ob_derived;
    ob_base = ob_derived;
    
    • This is a very important observation. Users of Optional would need such assignment (and construction) from optional<U> also. This requires more overloads and more complicated constraints:

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

      And apparently, it is not possible to get these conditions right: see here.

  3. Seth says:

    Nice exposition, especially pointing out the interference with other special members.

    Now Martinho Fernandes, Eric Niebler, Scott Meyers have written the same thing (in that order, AFAIR).

    Scott even bases a largish part of one of his talks on this conundrum.

    Did you see those? Do you have an opinion whether `is_base_of` should be used (as Eric expressly suggests)?

    Links

    http://flamingdangerzone.com/cxx11/2012/06/05/is_related.html

    http://ericniebler.com/2013/08/07/universal-references-and-the-copy-constructo/ (especially the comment exchange with Scott [here](http://ericniebler.com/2013/08/07/universal-references-and-the-copy-constructo/#comment-53))

    Can’t for the moment find Scott’s article/slides on this subject, but I trust you’ve seen it before.

  4. notlost says:

    Delegating constructors are *slightly* more elegant than an enable_if in the Wrapper case:

    template <class T> inline constexpr
    T const&
    constify (T& x) { return x; }
    
    Wrapper (Wrapper& w): Wrapper(constify(w)) {};
    

    The std::is_convertible check seems a sane thing to have if you’re forwarding, and know what you’re forwarding to, though

  5. notlost says:

    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 :-|

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

    • Hi James. You are probably referring to something like Dave Abraham’s article “Want Speed? Pass by Value.”, right?

      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.

  7. panqnik says:

    You might want to take a look on my post about virtual constructor in C++, here:

    http://blog.panqnik.pl/dev/co-variant-and-virtual-constructor-in-c/

    Pozdrawiam :)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s