Exploring ref qualifiers in C++
published at 21.05.2026 16:28 by Jens Weller
Save to Instapaper Pocket
Ref qualifiers are today an old C++11 feature, and recently I wanted to know more about them. Especially their potential use cases.
Thats a particular point with this feature, I've seen examples - but often without a compelling use case. This feature is a great way to achieve very specific things in C++. You can add a kind of fine tuning with that to your member functions, creating specializations useful in an rvalue context. Sounds good, right?
As ref qualifiers are only allowed on member functions, because they qualify this member function for selection in that value context, similar to the const qualifier:
struct refqualified{
mytype var;
const mytype& getVar()const&{return var;}
void setVar(const mytype& v)&{var=v;}
mytype getVar()&&{return var;}
};
In this simple example a class holds a member variable, its getter returns a const reference, calling getVar() from a temporary/rvalue context will lead to a compilation error, due to the reference being an lvalue. Similar for the setter, in this design the goal is to achieve that only in an on rvalue context one can call setVar. The rvalue ref qualified getter returns a copy of the variable, this could also be a move. You can combine a ref qualifier with =delete, than this produces a compilation error if called in this context.
So these are the ref qualifiers in C++:
- f(...)const& // combination of const qualifier with lvalue ref qualifier - access in const context is allowed
- f(...)& // lvalue ref qualifier, access from temporaries and other rvalue contexts denied. e.g. T{}.f() will not compile
- f(...)&& // select this function if this instance (e.g. this) is in an rvalue context like a temporary T{}.f() will select this version
These can be combined with member functions with the same signature without a ref qualifier, so you can add && qualified member functions to deal with rvalues, but not provide overloads for const& and & ref qualified calls. Ref qualifiers do not apply to constructors and destructors. As this allows for specializing for lvalue/rvalue context, it can be used to allow conversions or prevent them with f()&&=delete;
One other potential use case for ref qualifiers is to allow a fluent interface for setting various members and/or calling functions, but only until the instance has been assigned to a variable: auto l = createLogger().setSink("mysink").setDebug(true);
enum loglevel{NONE,SOME,FULL};
struct Logger
{
Logger(){};//default values
loglevel level=loglevel::NONE;
std::string sink="none";
bool debug=false;
Logger&& setLogLevel(loglevel ll)&&{level=ll;return std::move(* this);}
Logger&& setSink(std::string s)&&{sink = s;return std::move(*this);}
Logger&& setDebug(bool d = true)&&{debug=d;return std::move(*this);}
};
This can be expanded to the builder design pattern, where a specific class takes on the task to build an object of a said type. But I’ve tried to keep it simple, so no constexpr or noexcept - which could make sense in this context. This is an example “logger” class, which has a default constructor, but one wants to offer the option to explicitly configure the logging object with a chained call of setter member functions. This is a known pattern from java, but I’ve also seen this fluent interface used in a few popular C++ libraries. The ref qualifier allows now to turn the call from a non r-value reference to the setting functions into a compiler error. Of course one could implement also the setter with an & ref qualifier to handle rvalues and return a reference.
The usage:
Logger l = Logger().setSink("mysink").setDebug();
//l.setLogLevel(loglevel::SOME);//error
std::println("sink:{}, debug:{}",l.sink,l.debug);
This serves more as an example for the ref qualifier, then a perfect builder pattern implementation in C++. You’ll find the code on Compiler Explorer. A more in depth discussion of the builder pattern with fluent interface can be found on stack overflow.
Further reading
An indepth article about ref qualifiers mentions its usage in conversions, when a resource holding class either moves or copies its resource - depending on lvalue/rvalue contexts.
Andreas Fertig points out in his blog that it allows you to handle temporaries returning a part of your object, like in a range for loop. Using std::move on the return of a && qualified member function is useful, as it prevents a copy.
Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!