Lvalue to rvalue reference binding

28,466
Insert(key, Value()); // Compiler error here

key here is Key&& key - this is an lvalue! It has a name, and you can take its address. It's just that type of that lvalue is "rvalue reference to Key".

You need to pass in an rvalue, and for that you need to use std::move:

Insert(std::move(key), Value()); // No compiler error any more

I can see why this is counter-intuitive! But once you distinguish between and rvalue reference (which is a reference bound to an rvalue) and an actual rvalue, it becomes clearer.

Edit: the real problem here is using rvalue references at all. It makes sense to use them in a function template where the type of the argument is deduced, because this allows the argument to bind to either an lvalue reference or an rvalue reference, due to reference collapsing rules. See this article and video for why: http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

However, in this case the type of Key is not deduced when the function is called, as it has already been determined by the class when you instantiated FastHash<std::string, ... >. Thus you really are prescribing the use of rvalue references, and thus using std::move fixes the code.

I would change your code to that the parameters are take by value:

template <typename Key, typename Value, typename HashFunction, typename Equals>
Value& FastHash<Key, Value, HashFunction, Equals>::operator[](Key key)
{
    //  Some code here...

    Insert(std::move(key), Value());

    //   More code here.
}

template <typename Key, typename Value, typename HashFunction, typename Equals>
void FastHash<Key, Value, HashFunction, Equals>::Insert(Key key, Value value)
{
    // ...
}

Don't worry too much about extra copies due to use of value arguments - these are frequently optimised out by the compiler.

Share:
28,466

Related videos on Youtube

Kristian D'Amato
Author by

Kristian D'Amato

Game &amp; GPU developer, and C++ fanboy.

Updated on January 22, 2020

Comments

  • Kristian D'Amato
    Kristian D'Amato over 4 years

    The compiler keeps complaining I'm trying to bind an lvalue to an rvalue reference, but I cannot see how. I'm new to C++11, move semantics, etc., so please bear with me.

    I have this function:

    template <typename Key, typename Value, typename HashFunction, typename Equals>
    Value& FastHash<Key, Value, HashFunction, Equals>::operator[](Key&& key)
    {
        //  Some code here...
    
        Insert(key, Value()); // Compiler error here
    
        //   More code here.
    }
    

    which calls this method:

    template <typename Key, typename Value, typename HashFunction, typename Equals>
    void FastHash<Key, Value, HashFunction, Equals>::Insert(Key&& key, Value&& value)
    {
        // ...
    }
    

    I keep getting errors like the following:

    cannot convert argument 1 from 'std::string' to 'std::string &&'
    

    on the Insert() call. Isn't key defined as an rvalue in the operator overload? Why is it being reinterpreted as an lvalue?

  • Admin
    Admin over 10 years
    Isn't the type of the lvalue just "Key"? In what way is the rvalue-reference-ness still preserved?
  • Kristian D'Amato
    Kristian D'Amato over 10 years
    This works, but I'm not sure I understand the issue that hvd has raised. Could you explain?
  • Admin
    Admin over 10 years
    @hvd It is preserved because there is no argument type deduction going on her - see my edit.
  • Admin
    Admin over 10 years
    @polkadotcadaver I deleted my answer, because yours does a far better job. The question I had was a language lawyery question. The parameter key has type Key&&, but unless my understanding of C++ is wrong here, the expression key is an lvalue of type Key, not an lvalue of type Key&&.
  • Admin
    Admin over 10 years
    @hvd I see what you mean, interesting. I will have to do some digging to answer that! In this case, of course, it's the fact that it's an lvalue at all which is the issue.
  • dyp
    dyp over 10 years
    @hvd You're right; lvalueness is a property of an expression, and expressions don't have reference type, see [expr]/5.
  • Kristian D'Amato
    Kristian D'Amato over 10 years
    Thanks guys. I will pick the answer shortly. I think I need a primer on rvalues and move semantics, haha. When something new happens with a language, I trip over my feet trying to get the new features used EVERYWHERE in my code. I guess that wasn't called for here.
  • dyp
    dyp over 10 years
    @polkadotcadaver The reason why key is an lvalue is because it's an id-expression referring to a variable, see [expr.prim.general]/8. Essentially, all id-expressions but enumerators (and maybe some other) are lvalues.
  • Kristian D'Amato
    Kristian D'Amato over 10 years
    @DyP, are those references to the C++ standard and/or are they available online?
  • dyp
    dyp over 10 years
    @KristianD'Amato Yes; for C++11, I'm typically using draft n3485, which is publicly available for free, and incorporates some fixes to the Standard (which itself is not free). For the most recent draft, including C++1y features, you can use the cplusplus draft github repository.
  • dyp
    dyp over 10 years
    @polkadotcadaver If you don't need a copy of the key, you could (should) take it by const&.
  • Admin
    Admin over 10 years
    @DyP You're right, I'm assuming too much about the "other code".
  • Rich
    Rich about 8 years
    @dyp An variable alone is also an expression, but variable can certainly have reference type. But as you commented, expressions don't have reference type. How to reconcile variable type and variable-name-alone expression type? I asked a question on here
  • M.M
    M.M about 7 years
    The type of the lvalue key is Key, not "rvalue reference to key". An expression never has reference type.