Barton-Nackman Trick
Inject non-template friend functions into a namespace via class template instantiation, discoverable exclusively through ADL at the point of use.
Barton-Nackman Tricksince C++98A technique that defines free functions as in-class friend definitions inside a class template, causing the compiler to inject a concrete, non-template function into the enclosing namespace upon instantiation β discoverable only via argument-dependent lookup.
Overview
When you write a friend function definition inside a class template body, the compiler does not produce a function template. At the moment the class template is instantiated with a concrete type, it injects a concrete, non-template free function into the namespace that encloses the class template. That function is invisible to ordinary unqualified lookup β it surfaces only when ADL examines the namespaces associated with one of its argument types.
Two properties set this apart from an equivalent function template written outside the class:
Non-template functions win overload resolution. A concrete function outranks a function template when both are viable candidates. This lets a class template provide type-specific operator implementations that take precedence over any more general template already in scope.
Each instantiation produces an independent function. Widget<int> and Widget<double> each inject their own operator< into the enclosing namespace. They do not share parameterised template machinery; each is a fully resolved, non-template overload.
The idiom is most often combined with the Curiously Recurring Template Pattern (CRTP) to build mixin base classes that donate operator sets to derived types. The derived class simply inherits from the base, and ADL finds the injected operators whenever objects of that type appear as function arguments.
Syntax
template<typename Derived>
class EqualityComparable {
// In-class friend definition: injected as a non-template free
// function when EqualityComparable<X> is instantiated.
// Not findable by ordinary unqualified lookup β only via ADL.
friend bool operator==(const Derived& lhs, const Derived& rhs)
{
return lhs.equals(rhs);
}
friend bool operator!=(const Derived& lhs, const Derived& rhs)
{
return !(lhs == rhs);
}
};The friend keyword does double duty: it grants the function access to the class's private members, and it causes the definition to be placed in the enclosing namespace rather than treated as a member function.
Examples
Comparison mixin via CRTP
A base class donates six comparison operators given only two primitives on the derived type.
template<typename Derived>
class TotallyOrdered {
friend bool operator==(const Derived& a, const Derived& b) { return a.equals(b); }
friend bool operator!=(const Derived& a, const Derived& b) { return !a.equals(b); }
friend bool operator< (const Derived& a, const Derived& b) { return a.less_than(b); }
friend bool operator<=(const Derived& a, const Derived& b) { return !(b < a); }
friend bool operator> (const Derived& a, const Derived& b) { return b < a; }
friend bool operator>=(const Derived& a, const Derived& b) { return !(a < b); }
};
class Temperature : public TotallyOrdered<Temperature> {
public:
explicit Temperature(double kelvin) : kelvin_(kelvin) {}
bool equals(const Temperature& o) const { return kelvin_ == o.kelvin_; }
bool less_than(const Temperature& o) const { return kelvin_ < o.kelvin_; }
private:
double kelvin_;
};
void demo() {
Temperature boil{373.15}, freeze{273.15};
bool b1 = boil > freeze; // true β ADL finds operator> injected for Temperature
bool b2 = freeze <= boil; // true
bool b3 = boil == boil; // true
}Temperature gains six operators without declaring a single one. ADL discovers them because Temperature is an argument and its associated class TotallyOrdered<Temperature> lives in the same namespace as the injected functions.
Strong scalar with per-tag arithmetic
Because each tag produces a distinct instantiation, arithmetic operators for different strong typedefs are completely separate non-template functions β mixing them is a compile error, not a runtime surprise.
template<typename T, typename Tag>
class StrongScalar {
public:
explicit StrongScalar(T v) : value_(v) {}
T get() const { return value_; }
friend StrongScalar operator+(StrongScalar a, StrongScalar b)
{
return StrongScalar{a.value_ + b.value_};
}
friend StrongScalar operator-(StrongScalar a, StrongScalar b)
{
return StrongScalar{a.value_ - b.value_};
}
friend StrongScalar operator*(StrongScalar a, T scalar)
{
return StrongScalar{a.value_ * scalar};
}
friend bool operator==(StrongScalar a, StrongScalar b) { return a.value_ == b.value_; }
private:
T value_;
};
struct MetreTag {};
struct KilogramTag {};
using Metres = StrongScalar<double, MetreTag>;
using Kilograms = StrongScalar<double, KilogramTag>;
void demo() {
Metres a{10.0}, b{5.0};
Metres c = a + b; // OK
Metres d = a * 2.0; // OK
// Metres e = a + Kilograms{1}; // Error: no viable operator+
}swap customisation point
The pattern is the canonical way to provide a type-specific swap that the standard library will find via ADL.
template<typename T>
class Buffer {
public:
explicit Buffer(std::size_t cap)
: data_(new T[cap]), cap_(cap) {}
friend void swap(Buffer& a, Buffer& b) noexcept // C++11 noexcept
{
using std::swap;
swap(a.data_, b.data_);
swap(a.cap_, b.cap_);
}
private:
T* data_;
std::size_t cap_;
};
void example() {
Buffer<int> x{16}, y{32};
swap(x, y); // ADL resolves to the friend β O(1), no copies
std::swap(x, y); // std::swap calls the friend via ADL since C++11
}Best Practices
Define operators as in-class friends, not as out-of-class function templates. The Barton-Nackman form keeps the operator body adjacent to the type it serves, eliminates the need to repeat template parameters at the definition site, and ensures each concrete type gets its own concrete overload rather than sharing a generic template.
Use CRTP + Barton-Nackman for operator mixins pre-C++20. A TotallyOrdered<Derived> base is the idiomatic pattern for donating a full comparison suite. In C++20 and later, auto operator<=>(const T&) const = default; covers most cases and should be preferred when the comparison is member-wise or key-based.
Provide swap customisation through this mechanism. Any type with non-trivial resources should inject a friend void swap(T&, T&) noexcept in-class. Standard algorithms use using std::swap; swap(a, b); which triggers ADL, finding the friend over the generic std::swap.
Keep injected function bodies thin. Each instantiation emits its own copy of the function body (though it is implicitly inline). Expensive logic belongs in a private static helper that all instantiations can share through ordinary function calls.
Common Pitfalls
Ordinary unqualified lookup never finds injected friends. Qualified calls or contexts where ADL is suppressed will silently fail to find the function.
template<typename T>
void correct(T a, T b) {
bool r = a < b; // ADL active β finds injected operator<
}
template<typename T>
void broken(T a, T b) {
bool r = operator<(a, b); // Qualified call suppresses ADL β not found
}Dependent contexts can strip the associated namespaces. When a type is passed as a template argument through another template layer, the associated namespaces for ADL may not include the one where the friend was injected. Test ADL resolution explicitly when building deeply generic code.
One-definition rule still applies. Because the injected function originates from a template, it is implicitly inline. But if you somehow provide diverging bodies in different translation units (e.g., through partial specialisations with conflicting in-class friends for the same type), the resulting ODR violation is undefined behaviour with no guaranteed diagnostic.
C++20 spaceship supersedes simple comparison mixins. For types whose ordering is entirely determined by a single member or a straightforward key, <=> with = default (C++20) replaces the six-operator CRTP mixin cleanly. The Barton-Nackman approach remains the right tool for heterogeneous comparisons, custom ordering semantics, or codebases targeting pre-C++20 standards.
Historical Context: Restricted Template Expansion (1994)
The technique was introduced by John J. Barton and Lee R. Nackman in their 1994 paper "Restricted Template Expansion" (published in C++ Report) and later consolidated in their book Scientific and Engineering C++ (Addison-Wesley, 1994). The problem they faced was concrete: early C++ compilers (pre-standardisation, around 1993β1994) did not support function template overloading. If you wrote two function templates with the same name but different signatures, many compilers would simply refuse to compile, or would silently pick one without proper overload resolution.
The symptom was painful for operator-heavy numeric code. Suppose you have templates Matrix<T> and Vector<T>, and you want operator+ for each. Writing two function templates named operator+ at namespace scope triggered the compiler defect. Barton and Nackman's insight was to sidestep function template overloading entirely by using non-template friend functions injected through class template instantiation. Because each instantiation produces a concrete, non-template function β not a function template β overload resolution among them is straightforward and worked even on broken compilers.
The mechanism was called restricted template expansion because the friend injection makes the operator available only in contexts where the exact concrete type is involved, not in an unbounded template context. This restriction was the point: it avoided the combinatorial overload-resolution problems that plagued early implementations.
Friend Injection Before the C++98 Standard
In the original Barton-Nackman formulation, the injected friend was visible to both ordinary unqualified lookup and ADL. This was non-standard behaviour tolerated by pre-standard compilers: the function was literally placed into the enclosing namespace and could be found by name anywhere in the translation unit after the class template instantiation.
The C++98 standard tightened the rules. Standardised compilers β following the specification β made injected friend functions invisible to ordinary unqualified lookup. Only ADL can find them. This was actually a breaking change for code that relied on the old injection semantics. However, for the operator-overloading use case, ADL is exactly the right discovery mechanism: a + b triggers ADL on both a and b, finds the friends injected for their types, and resolves correctly. The semantic intent of the idiom survived intact; only the mechanism was clarified.
The pre-standard behaviour (ordinary lookup finding injected friends) is sometimes called friend name injection and is deprecated in the sense that relying on it was never guaranteed by the standard. Modern code should rely exclusively on ADL discovery of injected friends.
How ADL Makes the Modern Pattern Work
Understanding why the Barton-Nackman trick succeeds requires understanding what ADL actually searches. When the compiler evaluates an expression like a == b where a and b are of type T, it:
- Gathers the set of associated namespaces and associated classes for
T. For a class type, this includes the namespace where the class is defined and all its base classes. - Searches those associated namespaces for any function named
operator==. - Merges those candidates with the results of ordinary unqualified lookup.
- Runs overload resolution on the merged candidate set.
A friend function defined inside a class template body is, after instantiation, a concrete non-template function that lives in the enclosing namespace β but is not inserted into that namespace's name table for the purposes of ordinary unqualified lookup. It exists only in the compiler's internal record of "functions associated with this class." Step 2 above finds it because the class template instantiation is in the set of associated classes.
This is precisely what symmetric binary operators need. Consider:
template<typename Derived>
struct EqualityBase {
friend bool operator==(const Derived& a, const Derived& b) {
return a.equals(b);
}
};
struct Point : EqualityBase<Point> {
int x, y;
bool equals(const Point& o) const { return x == o.x && y == o.y; }
};
Point p{1, 2}, q{1, 2};
bool same = (p == q); // ADL on p and q finds EqualityBase<Point> as associated class,
// discovers injected operator== β resolves correctlyThe key property is that operator== is a non-template function. When overload resolution considers both a non-template function and a function template of equal match quality, the non-template wins. This is why the Barton-Nackman form can coexist with general operator== templates (such as those provided by std::rel_ops) without ambiguity: the injected concrete function takes priority.
ADL also explains the failure mode: if the call context suppresses ADL β for example, a qualified call operator==(p, q) β the injected friend is not found. This is not a bug; it is a necessary consequence of how the C++ name lookup rules are specified.
The Classic 4-from-2 Derivation: CRTP + Barton-Nackman
The most common application of the trick is a CRTP base class that derives a full suite of comparison operators from two primitives supplied by the derived class. The derived class only needs to implement operator== and operator<; the mixin injects the other four using the Barton-Nackman friend mechanism.
template<typename Derived>
struct Comparable {
// All four derived operators are injected as concrete non-template
// free functions for each Derived instantiation. Found via ADL.
friend bool operator!=(const Derived& a, const Derived& b) {
return !(a == b);
}
friend bool operator>(const Derived& a, const Derived& b) {
return b < a;
}
friend bool operator<=(const Derived& a, const Derived& b) {
return !(b < a);
}
friend bool operator>=(const Derived& a, const Derived& b) {
return !(a < b);
}
};
class Version : public Comparable<Version> {
public:
Version(int major, int minor, int patch)
: major_(major), minor_(minor), patch_(patch) {}
// These two are member operators β or could be non-member; the mixin
// only calls them, it does not define them.
bool operator==(const Version& o) const {
return major_ == o.major_ && minor_ == o.minor_ && patch_ == o.patch_;
}
bool operator<(const Version& o) const {
if (major_ != o.major_) return major_ < o.major_;
if (minor_ != o.minor_) return minor_ < o.minor_;
return patch_ < o.patch_;
}
private:
int major_, minor_, patch_;
};
Version v1{1, 4, 0}, v2{2, 0, 0};
bool a = (v1 != v2); // injected operator!= from Comparable<Version>
bool b = (v1 <= v2); // injected operator<=
bool c = (v2 > v1); // injected operator>Each of the four injected operators delegates entirely to the two primitives. The implementations are intentionally one-liners so that each instantiation's copy of the body is trivially inlineable by the compiler β the "one copy per instantiation" cost exists at the source level but vanishes at the binary level.
The primitives (operator== and operator<) are defined as member functions above. This is the most common arrangement, but they can equally be defined as hidden friends on Version itself β what matters is that they are found when called on two Version objects, which they will be regardless of whether they are members or ADL-found friends.
Why Friend Rather Than Member in the Mixin
An alternative β defining operator!= and friends as member functions of Comparable<Derived> β is tempting but subtly wrong. A member function operator!=(const Derived&) has Comparable<Derived> as its implicit object type, not Derived. The signature after name lookup is bool operator!=(const Comparable<Derived>& this, const Derived& rhs), which does not match a call on two Derived objects without the implicit base class conversion. While the conversion works at runtime due to inheritance, it means the operator is not a peer: derived_obj != other goes through a base-class conversion on the left side.
The friend version has signature bool operator!=(const Derived& a, const Derived& b) β both sides are Derived. No base class conversion is needed, both arguments are found via ADL, and overload resolution sees the most specific type immediately. This symmetry is why the friend form is canonical.
Boost.Operators: A Production Implementation
Boost.Operators (boost/operators.hpp) is the mature library realisation of the Barton-Nackman comparable mixin. It has been available since Boost 1.11 (1999) and provides dozens of small mixin templates, each injecting a specific operator or small group of operators. The library handles edge cases β heterogeneous comparisons, arithmetic with scalars, mixed-type iterators β that a hand-rolled mixin would need to address separately.
#include <boost/operators.hpp>
// boost::totally_ordered<T> injects: !=, >, <=, >= given == and < on T
// boost::addable<T> injects: operator+ given operator+= on T
// boost::subtractable<T> injects: operator- given operator-= on T
class Rational
: boost::totally_ordered<Rational>
, boost::addable<Rational>
, boost::subtractable<Rational>
{
public:
Rational(long num, long den) : num_(num), den_(den) { reduce(); }
bool operator==(const Rational& o) const {
return num_ == o.num_ && den_ == o.den_;
}
bool operator<(const Rational& o) const {
return num_ * o.den_ < o.num_ * den_;
}
Rational& operator+=(const Rational& o) {
num_ = num_ * o.den_ + o.num_ * den_;
den_ = den_ * o.den_;
reduce();
return *this;
}
Rational& operator-=(const Rational& o) {
return *this += Rational{-o.num_, o.den_};
}
private:
void reduce() { long g = gcd(num_, den_); num_ /= g; den_ /= g; }
static long gcd(long a, long b) { return b ? gcd(b, a % b) : a; }
long num_, den_;
};
Rational half{1, 2}, third{1, 3};
bool less = (third < half); // boost::totally_ordered injects via Barton-Nackman
Rational sum = half + third; // boost::addable injects operator+Boost.Operators chains the CRTP bases using a technique sometimes called the empty base class optimisation (EBO) to ensure the chain of base classes adds zero bytes to the derived object's size, since all the mixin bases are stateless.
The library also handles heterogeneous operators (e.g., addable<T, U> injects T operator+(T, U) and T operator+(U, T)) and provides iterator-specific mixins (boost::iterator_helper, boost::random_access_iterator_helper) that implement the full iterator interface from a minimal set of primitives β a direct application of the same principle at larger scale.
Comparison with operator<=> (C++20)
C++20 introduced the three-way comparison operator operator<=>, often called the spaceship operator. A single defaulted spaceship declaration synthesises all six comparison operators in one line:
#include <compare>
class Version20 {
public:
Version20(int major, int minor, int patch)
: major_(major), minor_(minor), patch_(patch) {}
// Generates ==, !=, <, <=, >, >= β all as hidden friends.
// Member-wise lexicographic comparison in declaration order.
auto operator<=>(const Version20&) const = default;
private:
int major_, minor_, patch_;
};
Version20 v1{1, 4, 0}, v2{2, 0, 0};
bool b1 = (v1 < v2); // synthesised from <=>
bool b2 = (v1 != v2); // synthesised from == (which <=> also implies)The defaulted spaceship also synthesises operator== in C++20: when operator<=> is defaulted, the compiler implicitly generates a defaulted operator== as well, so you do not need to write it separately (unlike the non-defaulted case).
For the common case where comparison is member-wise and uses the natural ordering of each member, the Barton-Nackman CRTP mixin is largely obsolete in C++20. The spaceship approach is shorter, less error-prone, and expresses intent more directly.
When the CRTP Mixin Remains Useful
The Barton-Nackman pattern is still the right tool in several situations:
Pre-C++20 codebases. Any codebase targeting C++14 or C++17 cannot use operator<=>. The Comparable<Derived> mixin is the standard solution.
Custom or partial orderings. The default spaceship performs member-wise lexicographic comparison. When ordering does not follow member layout β for example, a Matrix ordered by Frobenius norm, or a Date object where comparison is on a normalised calendar representation β you must write operator< yourself. At that point, a CRTP mixin that derives the other five from it avoids repetition.
Heterogeneous comparisons. operator<=> can be written to compare different types (e.g., string_view vs string), but the defaulted form only handles same-type, same-member comparisons. Heterogeneous operator injection via Barton-Nackman mixins (as in Boost.Operators' boost::less_than_comparable<T, U>) is still a clean pattern.
Composable operator subsets. The Barton-Nackman/Boost.Operators model lets you opt into exactly the operators you want and no more. A type might be equality-comparable but not ordered. With operator<=> you either get everything (via default) or write it all yourself. With CRTP mixins you can inherit from EqualityComparable<T> alone.
std::rel_ops, Deprecation, and Decision Table
Before C++20, the standard library provided std::rel_ops (in <utility>) as a shorthand for the 4-from-2 derivation. It is a namespace, not a class template:
#include <utility>
struct Box {
int volume;
bool operator==(const Box& o) const { return volume == o.volume; }
bool operator<(const Box& o) const { return volume < o.volume; }
};
{
using namespace std::rel_ops; // brings in !=, >, <=, >= templates
Box a{10}, b{20};
bool r = (a != b); // std::rel_ops::operator!= found via using-directive
}std::rel_ops was deprecated in C++20 and removed from the recommended practice long before that, for two reasons. First, the using namespace std::rel_ops; directive is file-scoped and infects all types in the translation unit with four extra operator templates β including types that should not have them, leading to silent compile errors or incorrect overload resolution. Second, the templates are lower priority than user-defined functions but higher than nothing, creating subtle ambiguity when two different types have their own operator!= in scope.
The Barton-Nackman CRTP mixin avoids both problems: the injected operators are type-specific (only apply to Derived, not to everything), and they are non-template functions (outrank any competing templates).
The table below compares the options for providing a full comparison suite:
| Approach | C++ Version | Type-specific | Needs primitives | Overhead |
|---|---|---|---|---|
std::rel_ops | C++98 (deprecated C++20) | No β all types | == and < | None, but pollutes namespace |
| CRTP + Barton-Nackman | C++98+ | Yes β per Derived | == and < | Zero (inline) |
| Boost.Operators | C++98+ (library) | Yes | Varies per mixin | Zero (EBO) |
operator<=> defaulted | C++20+ | Yes | None (compiler-generated) | Zero |
operator<=> custom | C++20+ | Yes | Custom <=> | Zero |
C++23 deducing this mixin | C++23+ | Yes (deduced) | == and < | Zero |
For new code on C++20 or later, defaulted operator<=> is the first choice. For pre-C++20 code, or when non-trivial ordering logic is involved, the CRTP + Barton-Nackman mixin (hand-rolled or via Boost.Operators) remains the correct and idiomatic pattern. std::rel_ops should never appear in new code.
See Also
reference/idioms/adl-customizationβ the argument-dependent lookup mechanism that makes injected friend functions findable; essential background for understanding why this trick worksstd::rel_ops(deprecated in C++20) β the standard library's earlier approach to deriving relational operators from==and<, now superseded byoperator<=>operator<=>(C++20) β three-way comparison that generates the full comparison suite from a single definition, the modern replacement for comparison mixins in the common case