Parameter passing

Core semantics7 standards

In/out/in-out conventions, sinks, views, forwarding references.

Drill points
  • By-value vs const& crossover point
  • Sink params (value + move) vs perfect forwarding
  • Out-params vs multi-return with structured bindings
  • string_view / span<const T> as view parameters
  • Constraining forwarding references
  • ABI cost of passing unique_ptr by value

The modern baseline arrives with move semantics: const T& for cheap in-params, by-value + std::move for sinks, T&& + std::forward for perfect forwarding, and return-by-value for outputs.

void set_name(std::string name)        // sink: caller decides copy vs move
{ name_ = std::move(name); }

void draw(const Shape& s);              // in-param: observe only

template <class... Args>
void emplace(Args&&... args)            // perfect forwarding
{ construct(std::forward<Args>(args)...); }

No core change. Generic lambdas take auto&& and forward with decltype(x); std::make_unique completes the sink story for ownership transfer.

auto fwd = [](auto&& x) {
  consume(std::forward<decltype(x)>(x));
};
void adopt(std::unique_ptr<Widget> w);  // ownership transfer, by value

std::string_view becomes the idiomatic read-only string parameter (always by value). Guaranteed copy elision makes return-by-value the unconditional out-param replacement, and structured bindings make multi-return pleasant.

void log(std::string_view msg);        // by value, never const&

auto minmax(const std::vector<int>& v) {
  return std::pair{*std::min_element(v.begin(), v.end()),
                   *std::max_element(v.begin(), v.end())};
}
auto [lo, hi] = minmax(data);

std::span<const T> generalizes the view parameter to any contiguous range; concepts finally constrain forwarding references so they stop swallowing everything.

double sum(std::span<const double> xs);   // vector, array, C array...

template <std::convertible_to<std::string> S>
void push(S&& s) { items_.push_back(std::forward<S>(s)); }

Deducing this lets a member take its own object by value, collapses &/const&/&& overload sets into one, and std::move_only_function is the sink type for move-only callables.

struct Token {
  std::string text;
  // one function instead of three ref-qualified overloads:
  template <class Self>
  auto&& value(this Self&& self) { return std::forward<Self>(self).text; }
};
void on_done(std::move_only_function<void()> cb);

std::function_ref is the non-owning callable parameter (replaces const std::function& and template bloat), and std::optional<T&> expresses an optional in/out reference without a raw pointer.

void for_each_row(std::function_ref<void(Row&)> f);   // cheap, non-owning

void lookup(Key k, std::optional<Entry&> cache_hint = {});
C++29direction · not adopted

No adopted changes — the F.15-F.21 guidance is stable. Watch relocation follow-ups: trivially relocatable types (C++26) may make more by-value passing free across ABIs.

C++26 per the final working draft; C++29 rows are proposals and direction, not adopted features.