std::move

(0)

Creature(const std::string &name) : m_name{name} { }
  • A passed lvalue binds to name, then is copied into m_name.
  • A passed rvalue binds to name, then is copied into m_name.

(1)

Creature(std::string name) : m_name{std::move(name)} { }
  • A passed lvalue is copied into name, then is moved into m_name.
  • A passed rvalue is moved into name, then is moved into m_name.

(2)

Creature(const std::string &name) : m_name{name} { }
Creature(std::string &&rname) : m_name{std::move(rname)} { }
  • A passed lvalue binds to name, then is copied into m_name.
  • A passed rvalue binds to rname, then is moved into m_name.

As move operations are usually faster than copies, (1) is better than (0) if you pass a lot of temporaries. (2) is optimal in terms of copies/moves, but requires code repetition.


The code repetition can be avoided with perfect forwarding:

(3)

template <typename T, 
    std::enable_if_t<std::is_convertible_v<std::remove_cvref_t<T>, std::string>, int> = 0
>
Creature(T&& name) : m_name{std::forward<T>(name)} { }

You might optionally want to constrain T in order to restrict the domain of types that this constructor can be instantiated with (as shown above). C++20 aims to simplify this with Concepts.