0

I run into a class definition like this:

class Foo {
 public:
  explicit Foo(Another_class&& input);
}

I am wondering what's the benefit of doing so? Why we enforce the input a rvalue and any benefit of doing so? I don't see much benefit and I think it should be better to have an universal reference?

Something like this:

class Foo {
 public:
  template<typename T>
  explicit Foo(T&& input);
}
WhatABeautifulWorld
  • 3,198
  • 3
  • 22
  • 30
  • 1
    What do you mean by "better to have an unified reference"? Can you elaborate on what your alternative would be? – Nathan Pierson Aug 29 '21 at 03:59
  • This allows you to move from `input`. – Igor Tandetnik Aug 29 '21 at 03:59
  • I feel like this could be answered by a combination of ["what is move semantics?"](https://stackoverflow.com/a/3109981/1678770) and ["what are rvalues, lvalues, xvalues, and prvalues?](https://stackoverflow.com/q/3601602/1678770). RValues allow distinguishing destructive actions on a mutable reference (to move contents from it), as opposed to immutable actions such as copies. "Unified" references lead to dangerous code, such as [`std::auto_ptr`](https://en.cppreference.com/w/cpp/memory/auto_ptr) – Human-Compiler Aug 29 '21 at 04:02
  • After your edit: See [Why is forwarding reference constructor called instead of copy constructor?](https://stackoverflow.com/q/43307761/1678770). – Human-Compiler Aug 29 '21 at 04:08
  • It doesn't answer my question... My question is what's the benefit of using the current definition instead of the more generic one. – WhatABeautifulWorld Aug 29 '21 at 04:25
  • @WhatABeautifulWorld For one thing it allows the constructor to be implemented in a source file, potentially speeding up compilation and hiding implementation details. It documents the expected type. With the template version, there is nothing to indicate what that constructor actually needs. The first version will produce better error messages. The templated version may be unexpectedly greedy and hide other constructors where the arguments don't match exactly, it will also replace compiler-generated constructors. What benefit do you expect from the templated version? – François Andrieux Aug 29 '21 at 04:26
  • @FrançoisAndrieux with the current version, I won't be able to do: Another_class T; then construct by just: Foo(T), right? – WhatABeautifulWorld Aug 29 '21 at 04:33
  • *"I think it should be better to have an universal reference?"* -- Better? What if you don't know how to construct `Foo` from a `std::map< std::string, std::vector >`? If the only class that `Foo` can be move-constructed from is `Another_class`, what is the benefit of using a template where only one instantiation is viable? (You are endorsing the more complex approach, so I grant you the initial burden of establishing a benefit.) – JaMiT Aug 29 '21 at 05:10

1 Answers1

0

This question is mixing two different concepts together.

An rvalue-reference is usually used to indicate that the constructor or function will destructively mutate that object (such as moving the contents out, or removing internal data), which is part of idiomatic move-semantics. Like lvalue-references, this also works with conversions-to-base types (so a Derived&& can be passed to a Base&&). Since the argument is a specific type, it's tightly constrained only to types that are either exactly, or convertible-to, that reference.

Forwarding references, on the other hand, allow for forwarding any (possibly constrained) template-deduced-type and matches the reference type exactly. Using forwarding references has several considerations:

  1. Forwarding references can only be used with templates, since the type must be deduced. It is not always desirable to throw everything in template, since each different type will be a unique instantiation, and this may also relocate code from a source-file into a header-file (which can affect compilation times).

  2. Forwarding references are often unambiguously better matches than equivalent overloads. If you have the following:

    void do_something(const Foo&);
    template <typename T&&>
    void do_something(T&&);   
    
    ...
    
    auto f = Foo{};
    do_something(f);
    

    Then this will call do_something(T&&) and not do_something(const Foo&) because f is an lvalue reference of Foo, so the template is an unambiguously better match.

  3. How often do you really need any T type? This is often more-so in generic code to forward argument/data (hence being called forwarding references). In most user-code, forwarding references are not generally necessary.

    It makes more sense for a Person to be constructible from a string name, rather than allowing a user to pass something like a vector<int>::iterator

  4. There are no semantics associated with forwarding references, since it matches anything.

  5. As a template, you push any errors to instantiation time. If a user passes the wrong input to the forwarding-reference that is not properly constriained, you may get pages of template errors.

Fundamentally, they satisfy different things.


With that out of the way: rvalue-references could be considered beneficial since they are:

  • idiomatic
  • simple (KISS principle)
  • lightweight (one level of indirection, no additional instantiations that templates would have)
  • self-documenting (A Foo&& clearly requires a Foo instance; T&& accepts anything)

Ultimately which of the two is better depends on the job that needs to be done.


See Also:

Human-Compiler
  • 11,022
  • 1
  • 32
  • 59