It's super common to implement mix-ins, which is as based on templates as it's possible to be, with multiple inheritance, e.g.
struct featureA { int x; };
struct featureB { const char* foo{}; void print(); };
etcand then
template<typename... Args>
struct mixin: Args... {
template<typename... CtorArgs>
mixin(CtorArgs... args)
: Args{args}...
{ }
};
using foobar = mixin<featureA, featureB>;
void f() { foobar{123, "foo"}.print(); }Curiously recurring, even! [0]
Although what you're showing seems to be a variation on the curiously recurring template pattern, as the classes being mixed are listed 'symmetrically' without much regard for which will inherit from which. I found a blog post on 'mixin classes', which are like the CRTP but invert the inheritance relation. [1] What you've shown seems to be a third way.
[0] https://en.wikipedia.org/wiki/Curiously_recurring_template_p...
[1] https://www.fluentcpp.com/2017/12/12/mixin-classes-yang-crtp...
It is unfortunate that there is no explicit way to distinguish OOP is-a from code reuse, other than the presence of virtual functions in base classes, but in my 15 years of professional use of c++ I can't remember it ever being an issue.
And sometimes the same feature is one, sometimes the other. There is no shame in it. Language features are there to be used.