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

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

25 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 🙂

  8. FlowChi says:

    Really helpful article, great work!

    BUT: Do you have any idea how to make this work in case that you use the perfect forwarding scheme in a variadic template constructor :-)?! So if your interface looks something like

    template Wrapper( Args&&… args );

    I ran into the problem during implementation of a simple binary tree class…

    • Would this work:

      #include <type_traits>
      #include <iostream>
      using namespace std;
      
      template <typename U>
      constexpr inline bool NonSelf()
      {
        return true;
      }
      
      template <typename U, typename T, typename... Args>
      constexpr inline bool NonSelf()
      {
        using DecayedT = typename decay<T>::type;
        return !is_same<DecayedT, U>::value
            && !is_base_of<U, DecayedT>::value
            && NonSelf<T, Args...>();
      }
      
      #define ENABLE_IF(...) \
        typename std::enable_if<__VA_ARGS__>::type* = nullptr
      
      struct Wrapper
      {
        Wrapper(Wrapper const&) { cout << "copy"; }
        Wrapper(Wrapper &&) { cout << "move"; }
       
        template <typename... T, ENABLE_IF(NonSelf<Wrapper, T...>())>
        Wrapper(T&&...) { cout << "forward"; }
      };
      
      int main()
      {
          Wrapper w(1);
          Wrapper v(w);
      }
      
  9. Autsin says:

    I’ve used a boolean `std::integral_constant` in conjuction with `std::is_rvalue_reference` to work around this curiousity recently. Essentially just tag dispatching.

    “`c++
    #include
    #include

    struct foo{
    private:
    template
    static void bar( T&& t, std::integral_constant ){
    std::cout << "rvalue" << std::endl;
    }

    template
    static void bar( const T& t, std::integral_constant ){
    std::cout << "lvalue" << std::endl;
    }

    public:
    template
    static void bar( T&& t ){
    bar( std::forward(t),
    std::integral_constant< bool, std::is_rvalue_reference::value >() );
    }
    };

    int make(){ return 1.0; }

    int main(){
    foo::bar( make() );
    auto i = make();
    foo::bar( i );
    }
    “`
    Live example: http://cpp.sh/57itx

  10. Eduardo says:

    Hello, excellent article even today.
    Could you please explain why you convert the ENABLE_IF to an anonymous value, as opposed to leaving it with an empty type?
    Also, I can confirm g++ 4.8.3 and Clang build the variadic version in one of your replies

    • I have the following definition of ENABLE_IF:

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

      The std::enable_if has in fact two arguments. The second is the one tat would be later be used in ::type. Its default type is void, so If I didn’t want to use default values, I could write the same definition as:

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

      When a Boolean expression passed in __VA_ARGS__ evaluates to true, this template construct is equivalent to:

        void* = nullptr
      

      When I use the macro in a definition like:

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

      It expands to:

      template <typename T, void* = nullptr>
        Wrapper(T&&) { cout << "forward"; }
      

      So, I have a void* pointer as non-type template parameter with a default value. Almost like I had a single-parameter template.

      There is no “anonymous value” in the process.

      • Eduardo says:

        Thanks for the reply. I did not write a good question, this is what I meant:
        There is the option of leaving the extra template parameter as either a defaulted type or a defaulted non-type, both anonymous. Is there a difference? I ran into an issue with gcc when my code defaulted a template type parameter that did an SFINAE check, the workaround was to use a defaulted non-type template parameter (this is what I called “value”), so, I don’t know if there is an actual template constructor overload selection difference between using an anonymous value or anonymous type

        • How would such usage of type template parameters look like? Like this?:

          template <typename T, 
                          typename = typename std::enable_if<COND>::type>
            Wrapper(T&&) { cout << "forward"; }
          

          I remember I tried it once and it did not work when I tried to do an enable_if-based overloading (because two template declarations were identical, save for the default value, and this seemed too little, to make the overloading work).

        • Eduardo says:

          Yes, I think in GCC there is a difference, it seems a defaulted template type that is not used won’t disable an overload, whereas a defaulted non-type parameter (a value) will… I wonder if this is standard-compliant or a bug in GCC, will investigate and report back

        • I am pretty sure the problem is not with type template parameter versus value template parameter, but in the fact that in one case the enable_if is part of template parameter (and therefore part of the signature), whereas in the other case enable_if is only used as “initializer” for the omitted parameter and does not contribute to the template’s signature.

  11. Pingback: Variadic template issue - Tutorial Guruji

Leave a reply to Eduardo Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.