I would like to understand if it is possible to use a nested class of a leaf CRTP class in the base CRTP class. The example below demonstrates the problem.
#include <iostream>
using namespace std;
template<class T>
class A
{
protected:
T* asLeaf(void)
{return static_cast<T*>(this);}
T const* asLeaf(void) const
{return static_cast<T const*>(this);}
public:
struct Inner
{int a = 10;};
void dispInner(void) const
{std::cout << asLeaf()->inner.a << std::endl;}
// I would like to use T::Inner in this class, e.g.
// typename T::Inner mvInnerA;
// However, I understand that it is not possible to
// use it in the form that is stated above. Thus,
// I am looking for any possible workarounds.
};
class B: public A<B>
{
public:
struct Inner: public A<B>::Inner
{int b = 20;};
protected:
friend A<B>;
B::Inner inner;
public:
void dispInner(void) const
{
A<B>::dispInner();
std::cout << asLeaf()->inner.b << std::endl;
}
};
int main()
{
B b;
b.dispInner();
return 0;
}
EDIT
I would like to provide several further comments based on the feedback that I have received:
A
should be aware of the existence of inner
. However, I would like to define an object inner
of the type B::Inner
in A
instead of providing the definition of inner
in B
and using it in A
.B
and/or B::Inner
and of the reasons why this cannot be done. Thus, technically, the design problem does not have a solution. However, I am looking for a feasible workaround.I have already considered several alternative solutions:
B::Inner inner
in A
and use the member functions of A
to provide the functionality that allows to modify the A<B>::Inner
part of B::Inner inner
. A<B>::Inner
and B::Inner
explicitly (i.e. not as nested classes). However, I would prefer to avoid this, because, by design, it is not expected that any classes that do not derive from A
will need to interact with A<B>::Inner
or the classes that derive from A<B>::Inner
Both solutions that I have presented may be acceptable. However, I am looking for any feasible alternatives.
The standard say that:
A class is considered a completely-defined object type (or complete type) at the closing
}
of the class-specifier.
It follows that B
isn't a completely-defined object when you specialize A
as A<B>
. Therefore you can't expect to be able to access its members or types or whatever from within the definition of A
(even though you can call back the derived class from within the definition of a member method of A
, that is perfectly legal instead other than the purpose of the CRTP idiom).
In other terms, when you do this:
typename T::Inner mvInnerA
You have no guarantees that T
is a completely-defined object and that's why you get the error.
A few alternatives:
You can define mvInnerType
as a function instead of as a type and use it as a factory to create objects of type T::inner
:
[static] auto mvInnerA() {
return typename T::Inner{};
}
Use it either as:
auto foo = A<B>::mvInnerA();
Or:
auto foo = obj.mvInnerA();
The right form depends on the fact that you make it static
or not.
Note that you can still use the hidden type somehow, even if its name isn't accessible:
using HiddenType = decltype(A<B>::mvInnerA());
HiddenType foo = A<B>::mvInnerA();
You can define mvInnerA
using a template a alias declaration like this:
template<typename U = T>
using mvInnerA = typename U::Inner;
Then use it as:
auto foo = A<B>::mvInnerA<>{};
For the type T
is (let me say) indirectly used through U
only when mvInnerA
is instantiated, you don't have the problem mentioned above. The price to pay for that is the presence of an annoying <>
and the fact that one can pass a custom type to mvInnerA
.