Remember the ifstream
ifstreamQuick, what does this C++17 code print?
void foo(const std::istream&) {
puts("istream");
}
void foo(const std::ifstream&) {
puts("ifstream");
}
int main() {
std::fstream t;
foo(t);
}
(Wandbox.) That’s right, it prints “istream” —
the fstream class (input/output, file-based) is derived from istream (input, non-file)
and also from ostream (output, non-file) but not from ifstream (input, file-based) and
not from ofstream (output, file-based).
There is no inheritance relationship
between fstream, ifstream, and ofstream. But there is an inheritance relationship between
fstream and iostream. The spaghetti of C++98-era multiple inheritance looks like this:

Quick, what does this C++2a Working Draft code print?
void foo(Same<int> auto) {
puts("exactly int");
}
void foo(Integral auto) {
puts("integral");
}
void foo(Regular auto) {
puts("regular");
}
int main() {
int t = 42;
foo(t);
}
Okay, trick question. This code doesn’t compile: out of Same<int>, Integral, and Regular, none
of them subsumes any of the others, so the overload resolution is ambiguous. (This is not a bad thing,
in this case! Nobody should be writing code like the above.)
The spaghetti of C++2a-era multiple subsumption looks like this:

Just like an old-time cartographer, I have filled in the blank space on the west side of my map
with non-sequitur dragons; in this case RegularInvocable.
(That’s right, there is no relationship between Regular and RegularInvocable — the C++2a Ranges
proposal introduced the term Regular to the standard library and immediately overloaded it with
multiple unrelated meanings.)
To be fair, I haven’t come up with an easy surprising example such as my ifstream example
above. The best I can do is stuff like
-
Same<T, int>does not subsumeIntegral<T> -
Same<T, int>does subsumeSame<int, T>(!), and vice versa -
Same<T, Base>does not subsumeDerivedFrom<T, Base> -
SwappableWith<T, T>does not subsumeSwappable<T>, nor vice versa
Concepts are a lot weirder than classical inheritance, because they’re parameterized. As you can see
above, Constructible<T> is a different concept (with different “derived concepts”)
from Constructible<T, T>. And so we can get extra weirdnesses such as
DerivedFrom<T, std::ifstream>does not subsumeDerivedFrom<T, std::istream>
So in conclusion: Remember the ifstream! Resist designing libraries with many couplings; resist implementing couplings on an ad-hoc basis. Today’s cutesy “because-we-can” spaghetti graph is tomorrow’s “but-why-would-you?” spaghetti graph.
