Virtual Friend Function
Simulate virtual behavior in non-member friend functions by delegating to a protected virtual member function.
Virtual Friend Functionsince C++98A pattern that grants non-member friend functions polymorphic behavior by having them delegate to a protected or private virtual member function, working around the fact that friend functions cannot themselves be declared virtual.
Overview
Friend functions in C++ are non-member functions with privileged access to a class's private and protected members. Because they are non-member functions, they cannot be declared virtual β virtual dispatch requires a receiver object and a vtable slot, neither of which exist for free functions.
This creates a practical problem: non-member functions that are part of a class's interface β most notably operator<<, operator>>, and comparison operators β often need to behave polymorphically across a class hierarchy. A base class pointer or reference should dispatch to the correct derived-class logic, but there is no direct mechanism to achieve this with a free function.
The Virtual Friend Function idiom resolves this by splitting responsibility across two functions:
- A non-virtual friend in the base class, which provides the non-member interface and is found by argument-dependent lookup (ADL).
- A virtual (often pure virtual) protected member, which the friend delegates to and which derived classes override.
The friend function never changes. It is declared once in the base class and is not inherited β friend declarations are not subject to inheritance in C++. The virtual member is what travels down the hierarchy.
Syntax
class Base {
public:
virtual ~Base() = default; // C++11: = default
// Non-virtual friend: the public interface
friend std::ostream& operator<<(std::ostream& os, const Base& obj) {
return obj.print(os); // delegates to virtual
}
protected:
// Virtual hook: derived classes customise here
virtual std::ostream& print(std::ostream& os) const = 0;
};
class Derived : public Base {
protected:
std::ostream& print(std::ostream& os) const override { // C++11: override
return os << "Derived{}";
}
};The print member is protected to prevent callers from bypassing the friend and invoking it directly. Marking it pure virtual (= 0) forces every concrete derived class to provide an implementation.
Examples
Polymorphic output streaming
The canonical use case is operator<<. Without this idiom, you would need to write a separate overload for every concrete type, or resort to dynamic_cast chains inside a single overload.
#include <iostream>
#include <memory>
#include <vector>
class Shape {
public:
virtual ~Shape() = default;
friend std::ostream& operator<<(std::ostream& os, const Shape& s) {
return s.print(os);
}
protected:
virtual std::ostream& print(std::ostream& os) const = 0;
};
class Circle : public Shape {
public:
explicit Circle(double radius) : radius_(radius) {}
protected:
std::ostream& print(std::ostream& os) const override {
return os << "Circle(r=" << radius_ << ")";
}
private:
double radius_;
};
class Rectangle : public Shape {
public:
Rectangle(double w, double h) : w_(w), h_(h) {}
protected:
std::ostream& print(std::ostream& os) const override {
return os << "Rect(" << w_ << "x" << h_ << ")";
}
private:
double w_, h_;
};
int main() {
// C++11: initializer list + unique_ptr; C++14: make_unique
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(3.0, 4.0));
for (const auto& s : shapes) { // C++11: range-for, auto
std::cout << *s << '\n'; // dispatches correctly via vtable
}
// Output:
// Circle(r=5)
// Rect(3x4)
}The single operator<< overload on Shape& handles the entire hierarchy. ADL ensures it is found whenever a Shape subtype is streamed.
Extending the pattern beyond output
The idiom is not limited to streaming. Any non-member operation that requires polymorphism can use it. Here, a serialisation framework needs a to_json() free function that behaves polymorphically:
#include <string>
class JsonSerializable {
public:
virtual ~JsonSerializable() = default;
friend std::string to_json(const JsonSerializable& obj) {
return obj.serialize();
}
protected:
virtual std::string serialize() const = 0;
};
class Point : public JsonSerializable {
public:
Point(int x, int y) : x_(x), y_(y) {}
protected:
std::string serialize() const override {
return "{\"x\":" + std::to_string(x_) +
",\"y\":" + std::to_string(y_) + "}";
}
private:
int x_, y_;
};
class NamedPoint : public Point {
public:
NamedPoint(int x, int y, std::string name)
: Point(x, y), name_(std::move(name)) {} // C++11: move
protected:
std::string serialize() const override {
// Call parent's virtual, then augment
std::string base = Point::serialize();
base.pop_back(); // strip closing '}'
return base + ",\"name\":\"" + name_ + "\"}";
}
private:
std::string name_;
};to_json() is a free function usable in generic code and found by ADL, yet it dispatches to the right derived implementation.
With C++20 concepts for type-safe constraints
Since C++20, you can constrain template parameters using concepts to ensure a type participates in this protocol:
#include <concepts>
#include <ostream>
template <typename T>
concept Printable = requires(const T& t, std::ostream& os) {
{ t.print(os) } -> std::same_as<std::ostream&>; // C++20: concept + requires
};
// Generic algorithm only accepts types with a print() member
template <Printable T>
void log(std::ostream& os, const T& obj) {
obj.print(os) << '\n';
}Note that exposing print as public to satisfy a concept trades the encapsulation advantage of the idiom for generic interoperability. Whether this is acceptable depends on your API contract.
Best Practices
Make the virtual hook protected, not public. The friend function is the intended public interface. Exposing the virtual member publicly creates a second, unsanctioned entry point that bypasses any pre/post-processing in the friend.
Declare the hook pure virtual in abstract bases. = 0 ensures every concrete class provides a definition and prevents accidentally inheriting a do-nothing default.
Return std::ostream& from print, not void. This allows the friend to return obj.print(os) in one expression, avoiding a silent mismatch where the friend returns os but print already flushed or moved it.
Name the private virtual consistently across your hierarchy. print, serialize, to_stream β pick one per concern and stick to it. Inconsistent naming makes it harder to audit which virtual hook a given friend delegates to.
Be mindful of friend non-inheritance. If a derived class wants to provide its own non-member overload for some reason, it must do so explicitly. There is no mechanism for a derived class to "inherit" the friend declaration.
Common Pitfalls
Object slicing through the friend. The friend must accept const Base&, not const Base by value. Passing by value slices the object, discards the derived portion, and the virtual call dispatches to the base's (possibly pure) print.
// WRONG: slices the object, virtual dispatch is broken
friend std::ostream& operator<<(std::ostream& os, const Shape s) {
return s.print(os); // always calls Shape::print, which may be pure
}
// CORRECT: reference preserves the dynamic type
friend std::ostream& operator<<(std::ostream& os, const Shape& s) {
return s.print(os);
}Forgetting that friend declarations are not inherited. New developers sometimes expect Derived to automatically "have" the same operator<< as Base via normal inheritance rules. In reality, the friend is re-found by ADL each time via the Base class, which works correctly as long as parameters are taken by Base&. If a function takes Derived& specifically, it needs its own friend declaration or the base's overload won't match.
Calling the virtual hook from the constructor or destructor. This is a general virtual-dispatch hazard, not unique to this idiom, but it surfaces here when print is called during construction (e.g., in a logging base constructor). During construction of Base, the dynamic type is Base, so the pure virtual print is invoked β undefined behaviour.
Exposing the hook as public to satisfy a concept or mixin. Doing so undermines the encapsulation rationale of the idiom. If external callers need direct access to print, reconsider whether the friend function is the right interface at all.
See Also
reference/idioms/virtual-constructorβ related pattern for simulating virtual behavior in object creationreference/idioms/adl-customizationβ how non-member friend functions are located by argument-dependent lookupreference/idioms/double-dispatchβ when two-argument polymorphism is required, extending the same delegation principle