Onward, Forward!

This entry is part of a series, RValue References: Moving Forward»

Besides providing move semantics, the other main application of rvalue references is in solving “the forwarding problem.” In this context, “forwarding” means passing a generic function’s actual argument on to a second function without rejecting any arguments that can be passed to that second function, without losing any information about the argument’s cv-qualification or l/rvalue-ness, and without overloading. In C++03, the best approximations turn all rvalues into lvalues and require two overloads.

Why Solve This Problem?

Consider this example:

template <class F>
struct unary_function_wrapper
{
     unary_function_wrapper(F f) : f(f) {}
 
     template <class ArgumentType>
     void operator()( ArgumentType x ) { f(x); }
 private:
     F f;
};
That formulation doesn’t work because our pass-by-value function call operator rejects all non-copyable/non-movable types, even though f might accept them. If we change the function to
template <class ArgumentType>
void operator()( ArgumentType& x ) { f(x); }
then we reject all non-const rvalues. We could add an overload:
template <class ArgumentType>
void operator()( ArgumentType& x ) { f(x); }
template <class ArgumentType>
void operator()( ArgumentType const& x ) { f(x); }
but like all the others, this one drops rvalueness, which we’d like to preserve so that f can take advantage of the move optimizations discussed in earlier articles. The need to introduce a second overload poses an additional problem: it doesn’t scale tractably to more arguments. A binary_function_wrapper would require four overloads, a ternary_function_wrapper would require eight, and in general, perfectly forwarding n arguments requires 2 overloads.

A Solution That Works

With rvalue references, we can take advantage of some specially-designed language rules to solve the problem this way:

template <class ArgumentType>
void operator()( ArgumentType && x )
{ f( std::forward<ArgumentType>(x) ); }
The two special rules in question are:
  1. A rule for collapsing rvalue references. In C++0x, it was long ago decided that if T is U&, then T& is also U&. That’s lvalue reference collapsing. Here’s how the rules were updated for rvalue references:

    • & + & yields &
    • & + && yields &
    • && + & yields &
    • && + && yields &&

    That is, any “lvalue-ness” at all makes the result into an lvalue.

  2. A rule for deducing “fully-generalized rvalue reference” parameters like ArgumentType above. The rule says that if the actual argument is an rvalue, ArgumentType will be deduced to be a non-reference type, but if the actual argument is an lvalue, ArgumentType will be deduced to be an lvalue reference type.

Here’s how these rules play out when the actual argument is an rvalue of type Y: ArgumentType is deduced to be Y, so there’s only one reference and no collapsing: the function parameter type is Y&&.

When the actual argument is an lvalue of type Y, ArgumentType is deduced to be Y& (or Y const&) and the reference-collapsing rules kick in, making the instantiated function’s parameter type Y& && or simply Y&… which is consistent with binding to an lvalue.

The final ingredient is the forward function, whose job is to “reconstitute” the actual argument’s rvalueness when ArgumentType is a non-reference and thus pass it on to f without interference.

Like std::move, std::forward is a zero-overhead operation. Although it’s not an exact translation, you can think of std::forward<ArgumentType>(x) as a descriptive way to say static_cast<ArgumentType&&>(x): when the actual argument is an rvalue, we cast x to an unnamed rvalue reference, but when the actual argument is an lvalue, ArgumentType is an lvalue reference and reference-collapsing kicks in, so the target of the static_cast is also an lvalue reference.

What does “forward” Really Mean?

Recently there’s been substantial disagreement about whether forward definition should be adjusted to accomodate uses other than “perfect forwarding,” e.g. to help with the move constructors of types such as std::tuple, which may contain reference members, and to prevent dangerous scenarios like binding lvalue references to members of an rvalue. The proposed adjustments are usually described as helping in cases where you want to “forward an X as a Y.” For details of these adjustments, see N2951.

I’ve never been comfortable with going in this direction because, while “forward a function’s argument, preserving cv-qualification and ‘rvalue-ness’” makes perfect sense to me, “forward an X as a Y” has no obvious meaning. In other words, there’s no obvious mental model for how the proposed forward should be used and what it’s for: we don’t have a programming model for it. I eventually gave up resisting the changes because I could see that it was useful in practice for solving a class of related problems that some users will have to deal with, but I still think we need to figure out what it means, and how to explain that clearly.

Posted Monday, December 7th, 2009 under Value Semantics.

44 Responses to “Onward, Forward!”

  1. Andrzej Krzemienski says:

    Hi, It was pointed out by a number of people that reference collapsing rules (that are required for perfect forwarding) are very confusing in other situations, where a type that looks like an r-value reference may in fact turn out to be an l-value reference. For example I write two overloads thereby enabling move-construction optimizations:

    void fun( Type const& elem ); // #1 
    void fun( Type && elem );     // #2 faster
     

    Both functions do not modify the argument ‘outside’. The second one will intercept temporaries and reuse them. Now, my algorith was fine and I want to generalize it by making it a template:

    template< typename Type >
    void fun( Type const& elem ); // #1 
     
    template< typename Type >
    void fun( Type && elem );     // #2
     

    The first one probably does what I expect, but the second may in fact take arguments by mutable l-value reference (due to perfect forwarding). I am not sure if I am making sense, as I do not fully understand the tricky behavior of r-val refs, and what happens if “enable_if” tricks or concepts come into play, but it may be dangerous.

    I thought that this problem would be solved (in some future C++ with concepts) by an introduction of a yet another concept that would bind ints, floats, pointers, arrays, functions, classes, but no references, and would not allow any CV qualifiers. I couldn’t find any such concept in former drafts. If it had some easy name, like “Object” (I know the name is already taken, but just pretend it isn’t), one could start teaching students C++ templates by saying, “you use templates as follows:

    template< Object T >
    void fun( T const& elem ); // #1 
     
    template< Object T >
    void fun( T && elem );     // #2
     

    “In a couple of years, when you are an expert, you can use “typename” instead of “Object”, to get more cool features like perfect forwarding, but you have to be aware of perils…” The above “Object” solution has two advantages: (1) it protects against reference collapsing (because we disallow references), and (2) it automatically makes all templates constrained (better error messages, etc.).

    Regards, &rzej

      (Quote)
    • litb says:

      This is making me wonder too. I object to the special handling of rvalue references. The issue at hand is to perfect forward, right? But why use rvalue references to do this? The issue rvalue references solve is to accept rvalues only, and to make overload resolution prefer them over const lvalues refs for binding from rvalues.

      Now your example of “T&&” shows something that’s not expected by most users, i think. Here is what i believe most expect

      // accept only rvalues. expected
      void f(ConcreteT&&);
       
      // accept only lvalues or const rvalues. expected
      template<typename T>
      void f(T&);
       
      // accept anything by deducing T to a reference type 
      // in case of lvalues. NOT expected
      template<typename T>
      void f(T&&);

      Why should we treat lvalues any special if the argument during deduction is an lvalue? I’m not understanding all the reasoning of rvalues, i think, nor do i have read all the papers out there about it. But i surely know this is counterintuitive.

      I think we should make perfect forwarding use a distinct technique, that only changes deduction and that does not treat rvalues any special. I propose the introduction of “ref template type parameters”. And i would like to know why they weren’t chosen for implementing forwarding (i don’t think i’m the first to come up with this idea). They look like the following:

      template<typename &T> // notice "&"!
      void f(T t);

      Deduction will deduce “T” to “U&” for lvalues of type “U”, and to “U&&” for rvalues of type “U”. This will allow perfect forwarding too, without the (imho) cursed special handling of rvalue references:

      // factory for T
      template<typename T, typename ...&U>
      T create(U ...u) { 
        return T(forward<U>(u)...);
      }

      The types in the parameter pack “U” are all either lvalue or rvalue references, depending on the lvalueness of the arguments of the call.

      Optionally, one may make “typename &T” forbid making “T” denote a non-reference type. But one can also go another way. Just like one can peel off a pointer modifier if we write “T*” from T, we can peel off an rvalue reference modifier:

      // this will have today's semantics as they are
      // without the '&' in front of T
      template<typename &T>
      void f(T &&);

      A call using an lvalue will deduce T to U&, and a call using an rvalue will deduce T to U (the && is part of the parameter type). Conversely, the following:

      template<typename &T>
      void f(T &);

      A call to f using an rvalue will deduce T to “U&&”, while using an lvalue will deduce T to “U”. An rvalue won’t be accepted if it’s nonconst, since reference collapsing will make the parameter have type “U&” (because “U&& &” == “U&”).

      Forward can be defined like the following:

      template<typename T>
      T forward(typename identity<T>::type t) {
        return static_cast<T>(t);
      }

      The “move” function will be written to deduce to reference types:

      template<typename &T>
      typename remove_reference<T>::type &&
      move(T t) { return static_cast<T>(t); } // no cast needed

      These proposed changes won’t need reference collapsing necessarily anymore to make perfect forwarding work, i think. But i can’t find a good reason to forbid them – they seem to make sense.

      Any opinions on this?

        (Quote)
      • litb says:
        The “move” function will be written to deduce to reference types:
        template<typename &T>
        typename remove_reference<T>::type &&
        move(T t) { return static_cast<T>(t); } // no cast needed
          

        The move code is a bit off. I meant the following for move:

        template<typename &T>
        typename remove_reference<T>::type &&
        move(T t) { return static_cast<typename remove_reference<T>::type&&>(t); }
          (Quote)
      • litb: The issue at hand is to perfect forward, right? But why use rvalue references to do this?

        The short answer is, “because we couldn’t think of another way.” And if you want to know why your idea almost certainly wouldn’t be accepted, it’s just because it’s now too late in the standardization cycle. All I can say is, “where have you been for the past 10 years?” I would have welcomed ideas like this one.

          (Quote)
        • Daveed Vandevoorde says:
          Dave Abrahams: The short answer is, “because we couldn’t think of another way.”
          Dave Abrahams: The short answer is, “because we couldn’t think of another way.”And if you want to know why your idea almost certainly wouldn’t be accepted, it’s just because it’s now too late in the standardization cycle.All I can say is, “where have you been for the past 10 years?”I would have welcomed ideas like this one.   

          I don’t think that’s quite right: I (and others) voiced my preference for not trying to fit everything under the rvalue reference type, and instead introduce forwarding-specific notation. E.g.:

          template void ff(for T) { … } // T deduced to & or &&

          (I also suggested not introducing rvalue reference types at all, and handle rvalue-vs-lvalue overloading with another parameter modifier.)

            (Quote)
          • Hi Daveed,

            That’s perfectly true; I wrote imprecisely. There were lots of other ideas raised, but you know as well as I do that there’s worlds of difference between “voicing a preference” and presenting a viable proposal. At one point (the previous Santa Cruz meeting), the committee sent us back to the drawing board to work on trying to realize some of the competing ideas. IIRC Jason Merrill was the only one who showed up for the in-depth conversations and his ideas turned out not to work. In fact, history is littered with approaches people thought were “obviously better,” that turned out to be fatally flawed in some way. I guess after seeing a few of those, I probably wasn’t ready to invest time in proposals that weren’t fairly complete and solid.

              (Quote)
            • Daveed Vandevoorde says:
              Dave Abrahams: Hi Daveed, That’s perfectly true; I wrote imprecisely.There were lots of other ideas raised, but you know as well as I do that there’s worlds of difference between “voicing a preference” and presenting a viable proposal.At one point (the previous Santa Cruz meeting), the committee sent us back to the drawing board to work on trying to realize some of the competing ideas.IIRC Jason Merrill was the only one who showed up for the in-depth conversations and his ideas turned out not to work.In fact, history is littered with approaches people thought were “obviously better,” that turned out to be fatally flawed in some way.I guess after seeing a few of those, I probably wasn’t ready to invest time in proposals that weren’t fairly complete and solid.   

              True.

              I just wanted to clarify that we (you, I , Howard, etc.) could think (and had thought) of other ways. There were from the start people (e.g., colleagues and I) who thought the merging of “moving” and “forwarding” was a terrible idea, and we sketched out “other ways” (where “from the start” means during informal discussions, before a formal rvalue reference proposal was made).

              My bad for not putting in the energy to push those sketches forward as a competing proposal.

                (Quote)
              • Was I around for any of these discussions? Just curious, ’cause I don’t remember them.

                  (Quote)
                • Daveed Vandevoorde says:

                  Yes: I distinctly remember you, Howard, John S., Steve A., and myself having that discussion over lunch (but I don’t recall the venue). (I think there were others — not sure.) I believe that’s also the time when the && token was (mostly) settled on. John and I suggested parameter modifiers both for a bind-reference-to-rvalue mechanism and as a “deduct-to-forward” indication (I, informally, repeated that suggestion a couple of times later as the proposal took shape).

                  I should note that I did not make the alternative suggestion because of specific issues I foresaw: It was just an intuition that introducing a new kind of reference would have difficult-to-tract ramifications and make the feature less accessible to the C++-programming community. (Had I anticipated the issues you and others have discovered since then, maybe I’d been more forceful. :-| )

                    (Quote)
    • Andrzej Krzemienski: The second one will intercept temporaries and reuse them. Now, my algorith was fine and I want to generalize it by making it a template:

      template< typename Type >
      void fun( Type const& elem ); // #1
       
      template< typename Type >
      void fun( Type && elem );     // #2

      The first one probably does what I expect, but the second may in fact take arguments by mutable l-value reference (due to perfect forwarding).

      Yep, this is a variation on the problem described in http://www.open-std.org/jtc1/sc22/WG21/docs/papers/2008/n2812.html. Of course it depends on #1 somehow being taken out-of-contention, but that can happen. You can avoid trouble by writing

      template< typename Type >
      void fun( Type && elem, Type* = 0 );     // #2

      to SFINAE out #2 when Type would be deduced as a reference type, but I grant you, that’s ugly.

        (Quote)
      • James Hopkin says:

        I don’t quite understand what you mean by:

        Dave Abrahams: Of course it depends on #1 somehow being taken out-of-contention

        Won’t #2 always be chosen for non-const lvalues? (gcc with the following code prints “move”)

          template< typename Type >
          void fun( Type const& ) // #1
          {
            std::cout << "copy";
          }
         
          template< typename Type >
          void fun( Type && )     // #2
          {
            std::cout << "move";
          }
         
          int main()
          {
            int n;
            fun(n);
          }
          (Quote)
        • Howard Hinnant says:

          Without constraints, yes, #2 will always be chosen non-const lvalues if “#1 isn’t taken out of contention”. What Dave means is to use “constrained templates”. My preference would be to constrain #2 to not accept lvalues (if I really wanted to write code this way):

          template< typename Type,
                    class = typename std::enable_if
                    <
                        !std::is_lvalue_reference<Type>::value
                    >::type>
          void fun( Type && )     // #2
          {
              std::cout << "move";
          }
          

          Now fun(n) outputs “copy”.

          Alternatively you could put all of the logic under #2 since it will accept everything:

          template< typename Type>
          void fun( Type && )     // #2
          {
              if (std::is_lvalue_reference<Type>::value)
                  std::cout << "copy";
              else
                  std::cout << "move";
          }
          
          // fun(n) -> copy
          

          That “run time if” could also be a compile-time-if:

          void fun_impl(std::true_type)
          {
             std::cout << "copy";
          }
          
          void fun_impl(std::false_type)
          {
             std::cout << "move";
          }
          
          template< typename Type>
          void fun( Type && )     // #2
          {
              fun_impl(std::is_lvalue_reference<Type>());
          }
          
          // fun(n) -> copy
          

          There are lots of options, and the best one depends on what you want to do. In practice I’ve found that I don’t usually want to overload T&& and const T& for a truly generic T. Far more often T is partially specialized. E.g. I usually want to overload on vector<T>&& and const vector<T>&.

            (Quote)
          • I guess the point is that the move/copy overloading pattern only applies when you know how to do the move; otherwise you might as well use a single function, with forwarding, to pass the argument on to something that does know how to move it. A generic T isn’t usually something you know how to move from.

            That said, this looks like another place where concepts reveal a flaw in the rvalue reference design. Something like

            struct X
            {
                template <SomeConcept T>
                X(T&&);
             
                template <SomeConcept T>
                X(T const&);
            };

            where it is known how to move from all models of SomeConcept, would be a problem. In C++ without concepts, you can have the same problem if you replace SomeConcept with a SFINAE constraint:

            struct X
            {
                template <class T>
                X(T&&, typename enable_if<some_constraint<T> >::type* = 0);
             
                template <class T>
                X(T const&, typename enable_if<some_constraint<T> >::type* = 0);
            };

            Still, as of today, no real use-case has cropped up that would cause the problem.

              (Quote)
    • Sebastian says:

      Reference collapsing is not to blame, IMHO. It’s the template argument deduction rule, §14.9.2.1/3 to be specific. This is going to confuse a lot of people. It already has. I’d also prefer a syntax that explicitly enables this deduction rule (or something similar).

      Cheers, Sebastian

        (Quote)
  2. Thomas Petit says:

    Thanks, Dave for this article !

    I’m currently writting an article on perfect forwading too, for a website in my native langage, and it helps a lot. Even so, I think i will avoid trying to explain the rules of collapsing or the inner mechanism of std::forward because I’m barely understand them. :)

    To illustrate the article, and trying to convey a bit of excitement, I search for cool example in the new standard library which take advange of perfect forwarding. So far, I found :

    make_shared : could be done in C++98 (boost does it) but way easier to implement in Ox. If I understand correctly, instead of constructing an object on the heap and pass it to the shared_ptr constructor wich will then allocate a reference counter (in a seperate place of memory), make_shared allow to stick the reference counter and the object in the same place, in one allocation.

    “emplace” family function : Like make_shared, somehow take advantage of a delay. Instead of eagerly build a temporary and copy it in a container, delay and construct directly inside the container. Can somehow be done in C++98 (with boost.in_place_factory) but painfully.

    operator() of std::ref. Allow to pass an heavy function-object with std::ref to STL algorithms because the std::ref::operator() now forward directly to the operator() of the underlying function-object. I’m not sure if it can be done in C++98, but it’s probably really hard, because even boost doesn’t do it.

    std::function, std::bind ? I’m not sure if perfect forwading is used here.

    Is there some other beautiful applications of perfect forwarding that I missed in the new SL ?

    And trying to see further in the future, is there some brand new stuff that cannot be done in C++98 and are now enabled by perfect forwarding ? If so, can you foreseen some cool application of it ?

      (Quote)
  3. Eelis says:

    I recently noticed that all this “perfect” forwarding appears to be a big sham.

    Suppose we wish to forward to the following function:

      void f(void(*)(int));
     

    Applying the idiom, we write the wrapper as follows:

      template <typename Arg> void wrap(Arg && x) { f(forward<Arg>(x)); }
     

    Now suppose have the following template:

      template <typename T> void g(T);
     

    Then we can call f with g:

      f(g); // ok!
     

    However, the wrapper cannot be called with g:

      wrap(g); // error: cannot resolve overload
     

    While the reason this fails is perfectly clear and understandable, it seems to me the conclusion is equally clear: this notion that C++1x’s rvalue references and std::forward facilitate “forwarding” (defined in the post as “passing a generic function’s actual argument on to a second function without rejecting any arguments that can be passed to that second function”), is a lie.

      (Quote)
    • Hi Eelis,

      Thanks for your post.

      I see your point about function templates, but to call “perfect forwarding” a “big sham” is perhaps overstating the case a bit. I think:

      1. When I wrote the definition of “perfect forwarding” in this article I simply failed to filter out the scenario you point out. So I could amend it to read “passing a generic function’s actual argument on to a second function without rejecting any argument objects that can be passed to that second function” (the name of a template is not an object).
      2. We simply overlooked that case when we came up with the phrase “perfect forwarding,”
      3. Even if we had noticed it, we probably still would have called it “perfect,” because—seriously, now—the implicit conversion of a function template name, which has no type at all, into a particular function pointer/reference type is a pretty esoteric thing, and these features cover all the normal cases perfectly.

      I also think you overstate your case in labelling “a lie” the claim that these features facilitate forwarding. They certainly do make forwarding possible and relatively easy, even if the result (or the article’s definition of forwarding) is ever so slightly imperfect.

        (Quote)
      • Eelis says:

        Hi Dave,

        Thanks for the reply.

        You’re right, of course, that my rhetoric was a bit excessive. It’s just that I’m excited about C++1x, and finding out about warts like these makes me sad and a little emotional. ;-)

        As for your third point; I think it’s not that esoteric at all. After all, one uses exactly this conversion every time one uses “cout << endl” (or other stream manipulators)! Indeed, that is the context in which I originally encountered this problem.

          (Quote)
        • Eelis says:

          More specifically, my use case was that of a simple unary print() function which streams its argument to std::cout. I had hoped that C++1x’s improved forwarding facilities would make it possible to write print() as a single function, but alas, it appears one still needs to write some overloads to make print(std::endl) work.

            (Quote)
        • Eelis: one uses exactly this conversion every time one uses “cout < < endl” (or other stream manipulators)! Indeed, that is the context in which I originally encountered this problem.

          Exactly. That is the only context in which one commonly encounters the use of this (mis-)feature. Anywhere else, a sensible person would have defined endl as an object instance, with a templated operator(). The fact that endl looks like an ordinary object in common code, and yet is not, is incredibly confusing when it breaks in generic contexts. Try

          #include <boost/lambda/lambda.hpp>
          // #include <boost/spirit/home/phoenix/core.hpp>
          // #include <boost/spirit/home/phoenix/operator.hpp>
          #include <iostream>>
           
          int main()
          {
            using namespace boost::lambda;
            // using namespace boost::phoenix::arg_names;
            (_1 << std::endl)(std::cout);
          }

          I haven’t bothered to look at the hoops they had to jump through in Boost.Phoenix to get this code to work as expected. And in case you think this is somehow the fault of Boost,

          #include <iostream>
           
          template <class T> int f(T const&);
          int x = f(std::endl);

          doesn’t compile either, for the same reasons. I maintain that this is an esoteric feature that probably should never have been put in the language to begin with, since we can do the same thing much more uniformly with function objects.

            (Quote)
          • Eelis says:

            Hm, putting the blame on the conversion feature is an interesting perspective that I had not considered. I agree it’s a defensible position. So I’ll agree that C++1x supports perfect forwarding for all “sanitary” cases not involving that conversion, while it’s merely an unfortunate historical accident that the standard library’s streams interface is insanitary. :-)

              (Quote)
      • Matt Calabrese says:

        As much as I love perfect forwarding, there is a more common case that fails that I’m sure plenty of people will accidentally bump into:

        void foo( int* );
         
        template< typename ParamType >
        void bar( ParamType&& arg )
        {
          foo( std::forward< ParamType >( arg ) );
        }
         
        int main()
        {
          foo( 0 ); // Fine
          bar( 0 ); // ***Kaboom***
        }

        Still, almost perfect is perfect enough.

          (Quote)
        • Eelis says:

          Wow, this case is far worse indeed! This is bad.. :(

            (Quote)
          • I don’t think it’s as bad as you’re making it out to be. You only have a right to expect these kinds of use cases to work in contexts where you already know the signature of the wrapped function (or its overload set). The normal case (e.g. in algorithms that invoke function objects) is that the caller already has to assume that 0 will be interpreted as an int, etc. So, e.g.,make_shared is slightly imperfect, but things like bind and lambda don’t suffer.

              (Quote)
          • Matt Calabrese says:

            It seems a lot worse than it really is. I can’t imagine this being a roadblock for anyone, it’s more just an annoyance since you’d have to do an explicit cast (or pass an object that is convertible to int*).

              (Quote)
    • litb says:

      Another case is this one:

      struct A { static int const value = 1; };
       
      void g(int);
       
      template<typename T>
      void f(T&&t) { g(forward<T>(t)); }
       

      Then calling

      g(A::value); 
       

      is fine, but calling

      f(A::value); 
       

      will fail to find a definition of A::value.

        (Quote)
      • Care to explain why you think so? This code compiles fine under g++-4.4.1 with -std=c++0x:

        include <utility>
         
        struct A { static int const value = 1; };
        int g(int);
         
        template<typename T>
        int f(T&&t) { return g(std::forward<T>(t)); }
         
        int x = f(A::value);
          (Quote)
  4. Joe Gottman says:

    From reading the N2951 paper, it seems to me that “forward<X>(y)” means “convert X to type Y without leaving a dangling reference”. The name forward does not connote this at all, but, by design, that is the effect of the function.

      (Quote)
    • That’s not quite right, but it’s close. The following wouldn’t leave a dangling reference even if it did compile.

      int x;
      int& y = forward<int&>(std::move(x));

      I think it would be simpler to say “without binding an lvalue reference to an rvalue.”

      Also, it’s not arbitrary type conversions, e.g. forward won’t convert a char const* to a std::string. But I bet if you keep pushing in that direction you can come up with something understandable and concise.

        (Quote)
      • Joe Gottman says:

        OK, how about “convert y to type X without binding an lvalue reference to an rvalue, creating a new object, or casting away const or volatile”.

          (Quote)
        • James Hopkin says:

          Wouldn’t any explanation also have to note that whether X is an lvalue reference or non-reference makes the expression an lvalue or rvalue respectively?

            (Quote)
        • You’re getting warmer, doc! Do we have to explain that forward<X>(y) returns an rvalue reference, or do you suppose that’s implied by “without…creating a new object?”

          Also, note that while we may in fact now have an accurate description of the language mechanics, it only points to the primary use-case through many layers of indirection. I’d rather call such a thing something_cast.

            (Quote)
  5. Dalle says:

    Is it really needed to explicitly write the template parameter? Can’t the template parameter for std::forward be deduced by the argument?

      (Quote)
    • Assuming you mean “deduced from the argument to forward,” not “deduced from the actual argument being forwarded,” then: nope. As noted in an earlier article, a named rvalue reference is an lvalue.* In fact, anything with a name is an lvalue. So you need some additional information to re-construct “rvalue-ness” of the actual argument being forwarded, and that information is captured in the deduced type ArgumentType (in the examples from this article).

      * In light of my response to Rodrigo, I should probably have written more precisely: “any expression that consists of a name is an lvalue expression”

        (Quote)
  6. Rodrigo says:

    Hi Dave!

    In my opinion, forward feels like a hack. I understand the “named rvalue” rule and I agree with it but… its not natural. I think its the only place in C++ where a type refuses to act like itself. When you teach rvalues you need to show the rationale of the rule too.

    I agree with your opinion about the role of forward. A better name for a function that changes the type is actually forward_as, quoting you in “forward an X as a Y”. Maybe we should have both functions? I don’t know if its worthy.

      (Quote)
    • Rodrigo: In my opinion, forward feels like a hack. I understand the “named rvalue” rule and I agree with it but… its not natural. I think its the only place in C++ where a type refuses to act like itself.

      I think your sense of discomfort probably comes from a misunderstanding about rvalues, and possibly from the name “rvalue reference.” I’m guessing you don’t know that r/lvalue-ness” is not a property of types, but of expressions, and that using the name “rvalue reference” for a type only reinforces your misperception. Am I correct, and does that help a bit?

      When you teach rvalues you need to show the rationale of the rule too.

      Naturally. I don’t think that’s problematic, do you?

      I agree with your opinion about the role of forward. A better name for a function that changes the type is actually forward_as, quoting you in “forward an X as a Y”.

      Then I guess you don’t really agree with my opinion ;-) . I don’t care what role forward has as long as it’s sensible and explainable, and it can be used to do what I understand to be forwarding. I don’t want to standardize anything we can’t explain, and since I’ve said nobody has explained to me what “forward X as a Y” means, I’d be especially unhappy with a forward_as function at this time. I would actually prefer to hide that additional functionality—if we must accept it without a clear explanation—under the name forward, since at least I think I understand what “forward the actual argument” means.

        (Quote)
      • Rodrigo says:

        I understand the rvalue stuff but what is a property of expressions and not of types made its place as a type signature. Indeed, the term rvalue reference is confusing (I had a hard time learning the real nature of move semantics). However I should say the proposal was beautiful because it could fit in C++ in a non-invasive way.

        About forward, I’d prefer to simply hide the extra functionality too. Otherwise it’s more a cast than a forward.

        But teaching forwarding_as is easy anyway! :( its just a generalization of forward.

          (Quote)
        • Rodrigo says:
          Rodrigo: But teaching forwarding_as is easy anyway! its just a generalization of forward.

          forward_as, not forwarding_as -_-

            (Quote)
        • Rodrigo: I understand the rvalue stuff but what is a property of expressions and not of types made its place as a type signature.

          Not really; rvalue itself never shows up as a “type signature” (if I understand what you mean by that phrase). It’s rvalue reference that appears in types. You just have to remember that “rvalue reference” is just a name for a kind of reference with different binding rules and slightly different meaning in expressions (it is treated as an rvalue unless named).

          Indeed, the term rvalue reference is confusing (I had a hard time learning the real nature of move semantics). However I should say the proposal was beautiful because it could fit in C++ in a non-invasive way. About forward, I’d prefer to simply hide the extra functionality too.

          I’m not sure what you mean by “hide,” but I just meant “consolidate.”

          Otherwise it’s more a cast than a forward. But teaching forward_as is easy anyway! :(its just a generalization of forward.   

          OK, then, teach it to me, because I don’t get it.

          A table of rules doesn’t work; that just leaves me with the idea that it’s a cast with some seemingly-arbitrary possibilities ruled out. The nearest I’ve been able to come to understanding it is something like: “this is what you use when you’re writing converting copy constructors and assignments for types with parts of arbitrary type that may turn out to be reference types, e.g. tuple<int&, long&, char const&>.” I don’t find that to be a very satisfying mental model for this operation. It seems like an arcane special case and makes me wonder why we’re expending valuable standard wording on it. I know that it’s not a contrived usage scenario, but it’s rare, and for me the value of standardizing it is strongly counterbalanced by its complexity.

            (Quote)
          • Rodrigo says:

            Special cases are the ones that make some things hard to learn and teach (even if not that hard, thats worse than no exceptions to the rules).

            In my ideal world, forward would be a language statement – forward(v) instead of forward{type_of_v}(v) – so I would rule out any intention to allow an arbitrary cast there. Even a macro is nicer

            @define forward(a) static_cast{decltype(a)}(a)

            (hash and brackets changed to avoid wordpress problems) and you need less typing. Not sure if it works always (and macros are evil) but typing the full std::forward form is annoying sometimes.

            Just my opinion.

              (Quote)

Leave a Comment (post replies using links below individual comments)

Spam Protection by WP-SpamFree

Subscribe without commenting