What does the & (ampersand) at the end of member function signature mean?

后端 未结 2 1380
暗喜
暗喜 2020-12-05 19:57

I can\'t remember which talk it was, but recently I watched some talks from CppCon 2017 and there someone mentioned as some kind of side-note, that the only true wa

相关标签:
2条回答
  • 2020-12-05 20:02

    As per http://en.cppreference.com/w/cpp/language/member_functions the & following your member function declaration is lvalue ref-qualifier.

    In other words, it requires this to be an l-value (the implicit object parameter has type lvalue reference to cv-qualified X). There is also &&, which requires this to be an r-value.

    To copy from documentation (const-, volatile-, and ref-qualified member functions):

    #include <iostream>
    struct S {
      void f() & { std::cout << "lvalue\n"; }
      void f() &&{ std::cout << "rvalue\n"; }
    };
    
    int main(){
      S s;
      s.f();            // prints "lvalue"
      std::move(s).f(); // prints "rvalue"
      S().f();          // prints "rvalue"
    }
    
    0 讨论(0)
  • 2020-12-05 20:08

    Ref-qualifiers - introduced in C++11

    Ref-qualifiers is not C++17 feature (looking at the tag of the question), but was a feature introduced in C++11.

    struct Foo
    {
      void bar() const &  { std::cout << "const lvalue Foo\n"; }
      void bar()       &  { std::cout << "lvalue Foo\n"; }
      void bar() const && { std::cout << "const rvalue Foo\n"; }
      void bar()       && { std::cout << "rvalue Foo\n"; }
    };
    
    const Foo&& getFoo() { return std::move(Foo()); }
    
    int main()
    {
      const Foo c_foo;
      Foo foo;
    
      c_foo.bar();            // const lvalue Foo
      foo.bar();              // lvalue Foo
      getFoo().bar();         // [prvalue] const rvalue Foo
      Foo().bar();            // [prvalue] rvalue Foo
    
      // xvalues bind to rvalue references, and overload resolution
      // favours selecting the rvalue ref-qualifier overloads.
      std::move(c_foo).bar(); // [xvalue] const rvalue Foo
      std::move(foo).bar();   // [xvalue] rvalue Foo
    }
    

    Note that an rvalue may be used to initialize a const lvalue reference (and in so expanding the lifetime of the object identified by the rvalue), meaning that if we remove the rvalue ref-qualifier overloads from the example above, then the rvalue value categories in the example will all favour the remaining const & overload:

    struct Foo
    {
      void bar() const & { std::cout << "const lvalue Foo\n"; }
      void bar()       & { std::cout << "lvalue Foo\n"; }
    };
    
    const Foo&& getFoo() { return std::move(Foo()); }
    
    int main()
    {
      const Foo c_foo;
      Foo foo;
    
      // For all rvalue value categories overload resolution
      // now selects the 'const &' overload, as an rvalue may
      // be used to initialize a const lvalue reference.
      c_foo.bar();            // const lvalue Foo
      foo.bar();              // lvalue Foo
      getFoo().bar();         // const lvalue Foo
      Foo().bar();            // const lvalue Foo
      std::move(c_foo).bar(); // const lvalue Foo
      std::move(foo).bar();   // const lvalue Foo
    }
    

    See e.g. the following blog post for for a brief introduction:

    • Andrzej's C++ blog - Ref-qualifiers

    rvalues cannot invoke non-const & overloads

    To possibly explain the intent of your recollected quote from the CppCon talk,

    "... that the only true way of overloading operator= ..."

    we visit [over.match.funcs]/1, /4 & /5 [emphasis mine]:

    /1 The subclauses of [over.match.funcs] describe the set of candidate functions and the argument list submitted to overload resolution in each context in which overload resolution is used. ...

    /4 For non-static member functions, the type of the implicit object parameter is

    • (4.1) — “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier

    • (4.2) — “rvalue reference to cv X” for functions declared with the && ref-qualifier

    where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration. ...

    /5 ... For non-static member functions declared without a ref-qualifier, an additional rule applies:

    • (5.1) — even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter. [ Note: The fact that such an argument is an rvalue does not affect the ranking of implicit conversion sequences. — end note ]

    From /5 above, the following overload (where the explicit & ref-qualifier has been omitted)

    struct test
    {
        test& operator=(const test&) { return *this }
    }
    

    allows assigning values to r-values, e.g.

    int main()
    {
        test t1;
        t1 = test(); // assign to l-value
        test() = t1; // assign to r-value
    }
    

    However, if we explicitly declare the overload with the & ref-qualifier, [over.match.funcs]/5.1 does not apply, and as long we do not supply an overload declared with the && ref-qualifier, r-value assignment will not be allowed.

    struct test
    {
        test& operator=(const test&) & { return *this; }
    };
    
    int main()
    {
        test t1;
        t1 = test(); // assign to l-value
        test() = t1; // error [clang]: error: no viable overloaded '='
    }
    

    I won't place any opinion as to whether explicitly including the & ref-qualifier when declaring custom assignment operator overloads is "the only true way of overload operator=", but would I dare to speculate, then I would guess that the intent behind such a statement is the exclusion of to-r-value assignment.

    As a properly designed assignment operator should arguably never be const (const T& operator=(const T&) const & would not make much sense), and as an rvalue may not be used to initialize a non-const lvalue reference, a set of overloads for operator= for a given type T that contain only T& operator=(const T&) & will never proviade a viable overload that can be invoked from a T object identified to be of an rvalue value category.

    0 讨论(0)
提交回复
热议问题