Template argument as rvalue reference

Discussion in 'C++' started by Juha Nieminen, Oct 26, 2012.

  1. There's a special rule in the new standard with regard to rvalue references
    when used with a template argument. Namely, if you have T&& (where T is
    a template argument), it will be collapsed to T& if an lvalue is given
    to it.

    This means that if you have something like this:

    template<typename T> void foo(T&&, T&&);

    then two different functions will be instantiated depending on whether
    you call it with rvalues or lvalues. In other words foo(1, 2) will
    generate one function and foo(a, b) (where a and b are existing variables)
    a different one.

    What I do not understand is why calling foo(a, 1) or foo(1, b) doesn't
    compile. It gives an error (at least with clang).
    Juha Nieminen, Oct 26, 2012
    #1
    1. Advertising

  2. Juha Nieminen

    SG Guest

    Am 26.10.2012 12:23, schrieb Juha Nieminen:> There's a special rule in
    the new standard with regard to rvalue references
    > when used with a template argument. Namely, if you have T&& (where T
    > is a template argument), it will be collapsed to T& if an lvalue is
    > given
    > to it.


    No, reference collapsing is always applicable. It is not restricted to
    the case of templates. The only restriction w.r.t. reference collapsing
    is that you cannot actually type things like

    typedef int && & lvalue_reference;

    But you can write

    typedef int&& rvalue_reference;
    typedef rvalue_reference& lvalue_reference;

    The special rule w.r.t. rvalue references is a deduction rule. That is,
    if a function receives a parameter of type T&& where T will be deduced,
    a special rule kicks in which might render T to be an lvalue reference.
    This is done depending on the argument you use to invoke the function
    template.

    > This means that if you have something like this:
    >
    > template<typename T> void foo(T&&, T&&);


    This is not the way to enable perfect forwarding.

    > then two different functions will be instantiated depending on whether
    > you call it with rvalues or lvalues. In other words foo(1, 2) will
    > generate one function and foo(a, b) (where a and b are existing
    > variables) a different one.
    >
    > What I do not understand is why calling foo(a, 1) or foo(1, b) doesn't
    > compile. It gives an error (at least with clang).


    Because it's a template argument deduction error.

    template<typename T> void foo(T&&, T&&);

    int main() {
    int a=0, b=0;
    foo(1,2); // --> T=int --> T&&=int&&
    foo(a,b); // --> T=int& --> T&&=int&
    foo(1,b); // deduction error
    }

    It's a deduction error, because the deduction of T for the first
    parameter yields T=int while the deduction of T for the second parameter
    yields T=int&&. This is inconsistent and qualifies as deduction failure.
    Since no other function or function template called foo is available,
    the compiler complains about not being able to call the right foo.

    Ok, so, you might think that if the compiler isn't able to figure out
    what T is ... how about specifying T? Let's try ...

    foo<int>(1,b); // error #1
    foo<int&>(1,b); // error #2

    #1 is an error because T=int makes the second parameter an rvalue
    reference but an rvalue reference of type int is not allowed to be
    initialized with an lvalue of int -- for safety reasons.

    #2 is an error because T=int& makes the first parameter an lvalue
    reference but an lvalue reference of type int is not allowed to be
    initialized with an rvalue of int -- for safety reasons, too.

    HTH,
    SG
    SG, Oct 26, 2012
    #2
    1. Advertising

  3. Juha Nieminen

    SG Guest

    Am 26.10.2012 12:38, schrieb SG:
    >
    > template<typename T> void foo(T&&, T&&);
    >
    > int main() {
    > int a=0, b=0;
    > foo(1,2); // --> T=int --> T&&=int&&
    > foo(a,b); // --> T=int& --> T&&=int&
    > foo(1,b); // deduction error
    > }
    >
    > It's a deduction error, because the deduction of T for the first
    > parameter yields T=int while the deduction of T for the second parameter
    > yields T=int&&. This is inconsistent and qualifies as deduction failure.

    ^^^^^^^
    Sorry, this was supposed to be "T=int&".

    > HTH,
    > SG
    SG, Oct 26, 2012
    #3
  4. SG <> wrote:
    > No, reference collapsing is always applicable. It is not restricted to
    > the case of templates.


    I think it is. If you have a non-templated rvalue reference parameter,
    you can't give it an lvalue. You'll get a compile error. (I think that
    it used to be for a long time that an lvalue could be given to something
    taking an rvalue reference, but the changed it much later in the
    standardization process.)

    The only situation where an rvalue reference is automatically collapsed
    to a regular reference is with templates.

    >> template<typename T> void foo(T&&, T&&);

    >
    > This is not the way to enable perfect forwarding.


    How is that relevant to my question?
    Juha Nieminen, Oct 26, 2012
    #4
  5. Juha Nieminen

    SG Guest

    Am 26.10.2012 13:20, schrieb Juha Nieminen:
    > SG <> wrote:
    >> No, reference collapsing is always applicable. It is not restricted to
    >> the case of templates.

    >
    > I think it is.


    Then you are wrong. Or perhaps you have a different idea about what
    "reference collapsing" means.

    > If you have a non-templated rvalue reference parameter,
    > you can't give it an lvalue. You'll get a compile error.


    What is an "rvalue reference parameter"?

    typedef int& foo;

    void bar(foo&& x);

    void test() {
    int a = 23;
    bar(a); // actually works
    }

    Is x of bar an rvalue reference parameter? Well, it looks like one. But
    it is not. Due to reference collapsing x is actually an lvalue
    reference. This is reference collapsing outside of the context of
    templates. Please don't confuse reference collapsing with template
    argument deduction.

    > The only situation where an rvalue reference is automatically collapsed
    > to a regular reference is with templates.


    I think you're mixing two different things: reference collapsing and
    template argument deduction. The former has nothing to do with templates.

    >>> template<typename T> void foo(T&&, T&&);

    >>
    >> This is not the way to enable perfect forwarding.

    >
    > How is that relevant to my question?


    I dunno. Maybe you're trying to do/understand perfect forwarding. At
    least it's the main reason why anybody would use T&& as function
    parameter type where T is deduced. In that case, the information that
    T&& should only be used for one parameter is useful.

    Cheers!
    SG
    SG, Oct 26, 2012
    #5
  6. Juha Nieminen

    SG Guest

    Am 26.10.2012 15:19, schrieb SG:
    > Am 26.10.2012 13:20, schrieb Juha Nieminen:
    >> SG <> wrote:
    >>> No, reference collapsing is always applicable. It is not restricted to
    >>> the case of templates.

    >>
    >> I think it is.

    >
    > Then you are wrong. Or perhaps you have a different idea about what
    > "reference collapsing" means.
    >
    >> If you have a non-templated rvalue reference parameter,
    >> you can't give it an lvalue. You'll get a compile error.

    >
    > What is an "rvalue reference parameter"?
    >
    > typedef int& foo;
    >
    > void bar(foo&& x);
    >
    > void test() {
    > int a = 23;
    > bar(a); // actually works
    > }
    >
    > Is x of bar an rvalue reference parameter? Well, it looks like one. But
    > it is not. Due to reference collapsing x is actually an lvalue
    > reference.


    Let me stress that this reference collapsing has nothing to do with the
    function call or the lvalue 'a' for that matter. Reference collapsing
    applies because I wrote "foo&&" where foo is already a reference. The
    reference collapsing rules turn foo&& into an lvalue reference as well,
    because "lvalue references win", or, to put it differently: & + && = &.

    > Cheers!
    > SG
    SG, Oct 26, 2012
    #6
  7. Juha Nieminen

    ptyxs Guest

    ptyxs, Oct 26, 2012
    #7
  8. Juha Nieminen

    ptyxs Guest

    ptyxs, Oct 26, 2012
    #8
  9. Juha Nieminen

    ptyxs Guest

    Le 26/10/2012 15:29, SG a écrit :
    > Am 26.10.2012 15:19, schrieb SG:
    >> Am 26.10.2012 13:20, schrieb Juha Nieminen:
    >>> SG <> wrote:
    >>>> No, reference collapsing is always applicable. It is not restricted to
    >>>> the case of templates.
    >>>
    >>> I think it is.

    >>
    >> Then you are wrong. Or perhaps you have a different idea about what
    >> "reference collapsing" means.
    >>
    >>> If you have a non-templated rvalue reference parameter,
    >>> you can't give it an lvalue. You'll get a compile error.

    >>
    >> What is an "rvalue reference parameter"?
    >>
    >> typedef int& foo;
    >>
    >> void bar(foo&& x);
    >>
    >> void test() {
    >> int a = 23;
    >> bar(a); // actually works
    >> }
    >>
    >> Is x of bar an rvalue reference parameter? Well, it looks like one. But
    >> it is not. Due to reference collapsing x is actually an lvalue
    >> reference.

    >
    > Let me stress that this reference collapsing has nothing to do with the
    > function call or the lvalue 'a' for that matter. Reference collapsing
    > applies because I wrote "foo&&" where foo is already a reference. The
    > reference collapsing rules turn foo&& into an lvalue reference as well,
    > because "lvalue references win", or, to put it differently: & + && = &.
    >
    >> Cheers!
    >> SG

    >
    ptyxs, Oct 26, 2012
    #9
  10. SG <> wrote:
    >> If you have a non-templated rvalue reference parameter,
    >> you can't give it an lvalue. You'll get a compile error.

    >
    > What is an "rvalue reference parameter"?


    Something like this:

    void foo(int&& i);

    If you try to give an lvalue to that, it gives an error.

    I must admit, I do not know the new standard enough to understand what
    exactly is going on here, and why it makes a difference (and why you need
    a typedef):

    > typedef int& foo;
    >
    > void bar(foo&& x);
    Juha Nieminen, Oct 27, 2012
    #10
  11. Juha Nieminen

    SG Guest

    Am 27.10.2012 21:12, schrieb Juha Nieminen:
    > SG <> wrote:
    >>> If you have a non-templated rvalue reference parameter,
    >>> you can't give it an lvalue. You'll get a compile error.

    >>
    >> What is an "rvalue reference parameter"?

    >
    > Something like this:
    >
    > void foo(int&& i);
    >
    > If you try to give an lvalue to that, it gives an error.


    Right. The thing is, if you write

    template<class T> void blah(T&& x) {...}

    x is not necessarily an rvalue reference parameter. It looks like one.
    And it may be one depending on what T is. But is also may be an lvalue
    reference. The reference collapsing rule "&+&&=&" makes x an lvalue
    reference in case T is an lvalue reference type, otherwise x is an
    rvalue reference. That's reference collapsing.

    In addition to reference collapsing we have a special template argument
    deduction rule which automatically picks the "right T" to make
    initializing a parameter of type T&& possible. That is, if the argument
    was an lvalue, the corresponding template type parameter is deduced to
    be an lvalue reference type, otherwise it is a non-reference type. Why?
    Because these rules are the rules they came up with to enable "perfect
    forwarding".

    > I must admit, I do not know the new standard enough to understand what
    > exactly is going on here, and why it makes a difference (and why you need
    > a typedef):
    >
    >> typedef int& foo;
    >>
    >> void bar(foo&& x);


    The syntax does not allow "int& && x". I guess that is because there
    would be no point in allowing it since you could just as well write
    "int& x".

    If you havn't already seen one of Scott Meyers' latest talks about
    rvalue references, I encourage you to take a look.

    HTH,
    SG
    SG, Oct 28, 2012
    #11
  12. Juha Nieminen

    ptyxs Guest

    ptyxs, Oct 29, 2012
    #12
  13. Juha Nieminen

    ptyxs Guest

    ptyxs, Oct 30, 2012
    #13
  14. Juha Nieminen

    Zhihao Yuan Guest

    On Friday, October 26, 2012 5:23:15 AM UTC-5, Juha Nieminen wrote:
    > template<typename T> void foo(T&&, T&&);


    Let's stop repeating the materials all over the internet. The
    following is exactly what you want:

    template<typename T1, typename T2> void foo(T1&&, T2&&,
    typename std::enable_if<std::is_same<
    typename std::remove_reference<T1>::type,
    typename std::remove_reference<T2>::type>::value>::type* = 0)
    {}
    Zhihao Yuan, Nov 20, 2012
    #14
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Kavya
    Replies:
    9
    Views:
    493
    Dik T. Winter
    Oct 28, 2006
  2. mrstephengross
    Replies:
    2
    Views:
    394
    mrstephengross
    Sep 7, 2005
  3. nw
    Replies:
    0
    Views:
    300
  4. Juha Nieminen
    Replies:
    13
    Views:
    599
    Edek Pienkowski
    Aug 29, 2012
  5. K. Frank
    Replies:
    4
    Views:
    157
    K. Frank
    Jul 17, 2013
Loading...

Share This Page