I am working on a library where the consumer writes a struct (Foo). Foo uses special types (A,B) and attributes (Attribute) that are provided by the library.
The library will instantiate Foo with different templates.
To minimize clutter, we use masks, i.e. multitudes of templates are hidden into just one template that is a derived of MaskBase. This also allows us to specialize Attribute with cleaner code, given one of the individual derived of Mask (here MaskA and MaskB).
My question is:
- Can the boiler plate in line i and line ii be removed?
The question is relevant to us because we want the consumer code to be as simple as possible, for the convenience of the consumer.
A minor subquestion is whether the presented use of concepts is intended practice, or whether the concept of inheritance could be used in a more immediate way.
#include<type_traits>
#include<concepts>
#include<iostream>
// base type declarations
struct MaskBase{};
// concept definitions
template<typename Tmask> concept is_Mask = ( std::is_base_of_v<MaskBase,Tmask> );
// mask definitions
template<typename T> struct MaskA: MaskBase{};
template<typename T> struct MaskB: MaskBase{};
// Example Attribute implementation
template<typename Tmask> requires is_Mask<Tmask> struct Attribute; // declaration
template<typename T> struct Attribute<MaskA<T>>{}; // definition for MaskA
template<typename T> struct Attribute<MaskB<T>>{}; // definition for MaskB
// Foo base definitions
template<typename Tmask> requires is_Mask<Tmask> struct FooBase;
template<typename T>
struct FooBase< MaskA<T> >{
using A = T;
using B = int;
};
template<typename T>
struct FooBase< MaskB<T> >{
using A = double;
using B = std::pair<T,int>;
};
// Foo implementation
template<typename Tmask>
struct Foo: FooBase<Tmask>{
//
using A = typename Foo::A; // (Line i)
using B = typename Foo::B; // (Line ii)
//
A x; // <-- the consumer would like to use types A and B like so in their implementation of Foo.
B v;
//
Attribute<Tmask> a;
};
// ++++ utilization
int main(){
Foo< MaskA<float > > fooA;
Foo< MaskB<double> > fooB;
};
Remark
I understand that the technical problem is related to the fact that Foo
is not a complete type at the time when the consumer writes A x
. Therefor, the line i is to signal to the compiler something like a forward declaration. Is this understanding accurate? If so, can this sort of declaration be moved into FooBase or somewhere where it does not need to be written by the consumer (and for each derived of FooBase that they implement)?
No, you have to have some boilerplate here, or at the use of
A
andB
, becauseA
andB
might not be types in some specialisations ofFooBase
.Unfortunately, it doesn't matter that there aren't any such specialisations, nor would it matter if you constrained
Tmask
such thatFooBase<Tmask>::A
had to be a type.