As described in crtp-pass-types-from-derived-class-to-base-class by Evg, the compiler is unable to deduce the type Impl::TType in the declaration of class Base:
template <typename Impl>
struct Base
{
typename Impl::TType t;
};
struct MyImplementation : public Base<MyImplementation>
{
using TType = int;
TType t;
MyImplementation(TType n) : t(n) {};
};
int main()
{
MyImplementation mi(3);
return 0;
}
However, I want a CRTP design where the user writes an implementation which derives from Base with the only requirement that the implementation class specifies the type TType so that the Base class can instantiate members of that type. In other words, the class Base expects the implementation to define type TType. For example, it may have to perform the operation on an integer but various implementations can use different int types (e.g. int, unsigned int, long, short ...). TType is thus implementation-specific and should not concern the class Base. The brute approach is to do:
template <typename Impl, typename T>
struct Base
{
T t;
};
struct MyImplementation : public Base<MyImplementation, T>
{
using TType = T; //uneccessary but left for consistency
TType t;
MyImplementation(TType n) : t(n) {};
};
But here the type TType does concern the Base and leaves more room for mistakes. For example, if a user wants to template TType, he/she is required to write class MyImplementation : public Base< MyImplementation<T>, T >, which is a bad API (especially if Base requires defining more than one type; e.g. public Base< MyImplementation<T, V, Q>, T, V, Q >), making it easy to make mistakes such as:
template <typename T, typename Q>
struct MyImplementation : public Base<MyImplementation<T, Q>, Q>
{
using TType = T; //user expects that Base will use T as TType, while Q will be used instead
};
Is anybody aware of a modern efficient approach to this?
This is what I ended up doing. It is not the nicest, but I think it is manageable and API does not suffer. Let me know if I missed something.
This will check if the implementation satisfies the concept
HasTTypebefore classBaseis instantiated and without any overhead. This is based on the fact that parent class' ctor is invoked before child class' ctor.Furthermore, if the class
Baseneeds to instantiate and manage objects of typeTType, it can do it using placement new. This has a small overhead because the size ofTTypeis unknown and more memory than necessary needs to be allocated, but this can be acceptable in most cases. The user can also pass the size ofTTypeas a template argument if he/she wants to avoid this overhead, but I consider this to be better than passingTTypesince the user mostly won't specify the size. Concept also needs to check that the size is not too small.The alternative is to pass
TTypeas a template parameter ofBase, which is in my opinion a bit worse API-wise, but at least the user can be assuredT=TType.