References, const correctness and value categories
Tell lvalues from rvalues, use std::move and std::forward correctly, and design interfaces that refuse to copy unnecessarily.
References and value categories
#include <utility>
#include <string>
#include <vector>
std::string name = "ada";
std::string& lref = name; // lvalue reference: binds to a named object
const std::string& cref = name; // read-only view, can bind to a temporary
std::string&& rref = std::move(name); // rvalue reference: an xvalue
// lvalue = has an identity, you can take its address
// rvalue = a temporary, on the right of an assignment
// xvalue = an object you have promised not to reuse (result of std::move)
void take_by_value(std::string s); // copies in, absorbs rvalues cheaply
void take_by_cref(const std::string& s); // never copies, never mutates
void take_by_rref(std::string&& s); // takes ownership of the argument| Parameter | Copies | Use when |
|---|---|---|
T value | One move or copy | You will store it, or T is cheap (int, pointer, string_view) |
const T& | None | You only read; the default for non-trivial types |
T& | None | You must modify the caller's object |
T&& | None | A move constructor or a sink parameter named t |
std::span<T> | None | A contiguous range you only read |
std::move does not move anything. It is a cast to an rvalue reference that makes the move constructor eligible. After std::move(x) you may not read x except to assign to it or destroy it.
Forwarding and copy elision
#include <utility>
#include <vector>
#include <string>
template <class T, class... Args>
T make(Args&&... args) { // forwarding references
return T(std::forward<Args>(args)...); // preserve value category
}
void sink(std::string&& s); // forward, do not re-move
template <class T>
void pass(T&& t) {
sink(std::forward<T>(t)); // correct for lvalue and rvalue callers
// sink(std::move(t)); // wrong: turns every caller argument into an xvalue
}
struct Widget {
std::string a;
std::vector<int> b;
Widget(std::string a_, std::vector<int> b_) // take by value
: a(std::move(a_)), b(std::move(b_)) {} // then move into the member
Widget(Widget&&) noexcept = default;
Widget& operator=(Widget&&) noexcept = default;
Widget(const Widget&) = default;
Widget& operator=(const Widget&) = default;
};
Widget build() {
Widget w{"x", {1, 2, 3}};
return w; // NRVO: constructed directly in the caller's storage
}
int main() {
auto w = build(); // no copy, no move: guaranteed copy elision in C++17
Widget other = std::move(w); // explicit move; w is now valid but unspecified
}- Writing
return std::move(local);defeats NRVO and can make the code slower, not faster. - After a move, the object is in a valid but unspecified state. Call
clear()if you want it reusable; never assume it is empty. - Only mark move operations
noexceptwhen they truly are, but do mark them:std::vectorreallocation uses move instead of copy only fornoexceptmove constructors. - A moving operation that throws leaves the source in an unknown state, which is why the standard library containers avoid it.
// const correctness as API documentation
class Buffer {
public:
std::size_t size() const noexcept; // does not mutate
char& operator[](std::size_t i); // mutable access
const char& operator[](std::size_t i) const; // read-only access
private:
std::unique_ptr<char[]> data_;
std::size_t size_ = 0;
};💡
Mark every member function that does not modify observable state as
const. A const method can be called on a const object or through a const reference, which is exactly what template code and the standard algorithms require.FAQ
Should I pass std::string by value or by const reference?
Take
const std::string& when you only read. Take by value when you store a copy anyway — one move for rvalue callers, one copy for lvalue callers — and then std::move it into the member. Do not take by rvalue reference unless you write a move constructor.What does std::forward do that std::move does not?
std::forward<T> preserves the caller's value category, so an lvalue argument stays an lvalue and an rvalue stays an rvalue. std::move unconditionally casts to an rvalue, which is wrong inside a forwarding function because it makes a copy unavoidable for lvalue callers.Related
Classes, constructors and RAII Templates, concepts and generic programming
Last refreshed 2026-09-18.