There are many useful cases of the idea mentioned by Arne. For instance, looking at Very basic tuple implementation, this is how a single tuple element is defined:
template <size_t N, typename T>
class TupleElem
{
T elem;
public:
T& get() { return elem; }
const T& get() const { return elem; }
};
It is templated on N
, without depending on it. Why? Because the tuple implementation
template <size_t... N, typename... T>
class TupleImpl <sizes <N...>, T...> : TupleElem <N, T>...
{
//..
};
derives multiple such elements, each with a unique N
, serving as an identifier. Without it, TupleImpl
would be deriving the same class twice, had two element types been identical within parameter pack T...
. Neither random access to elements would work in this case (via an explicit call of function get()
of the appropriate TupleElem
base class, which would be ambiguous), nor empty base optimization (via specializing TupleElem
for empty types T
to not have a data member of type T
).
This is a real use case, and exactly how std::tuple
is implemented by clang. Of course, a class like TupleElem
would be a hidden implementation detail, and not part of the interface. For instance, gcc follows an entirely different recursive class design.
In general, you will need to study the context where classes are used to understand the intent of the designer.