Crtp -- Accessing Incomplete Type Members

CRTP -- accessing incomplete type members

When a class template is instantiated, its members other than non-virtual member functions are instantiated together with it. Non-virtual member functions, however, are only instantiated when odr-used (basically, called or have their address taken).

When the compiler encounters class Implementation : public Interface<Implementation>, it needs to instantiate Interface<Implementation>. At this point, Implementation is still an incomplete type, its TYPE member has not yet been seen. On the other hand, Interface<Implementation>::foo is only instantiated later, when it's called in main. At that point, Implementation is a complete type.

CRTP and Incomplete Types

Using B<D> as base class for D requires B<D> to be a complete class. Hence it will cause implicit instantiation of B<D>.

The point of instantiation of the class specialization is immediately before the namespace scope declaration requiring it, meaning before the definition of D. (by [temp.point]/4)

The implicit instantiation of B<D> does not cause implicit instantiation of the member function definition for foo. Hence D won't be required to be complete here.


The definition

void bar() const { foo(); }

has an ODR-use of foo. Therefore it will cause implicit instantiation of B<D>::foo.

The points of instantiation for a member function specialization are immediately after the namespace scope declaration and at the end of the translation unit. (by [temp.point]/1 and [temp.point]/7.1)

Both of these are after the definition of D and therefore D will be complete at these points. Consequently static_cast<const D*>(this)->baz(); is not a problem.


Note that D is not a template. The template instantiation rules do not apply to it. It doesn't matter whether bar is used or referred to at all. Neither does it matter whether you use D d; d.foo(); or anything at all following the definition of D.

Accessing a member type of an incomplete type

I'm pretty sure you can't use the CRTP on a 'using' case. You can use it for methods and members, but not things like types. When using templates though, having types as template parameters is what it is so useful for, so why not do

template <typename Derived, typename Type>
....

Which will work perfectly fine.

Incomplete type while accessing templated derived class (CRTP)' static function while doing SFINAE

To workaround the problem you could delay type instantiation by making T of sfinae_func additional template parameter's default value e.g. as follows (oh and don't forget to use inner type of std::enable_if to actually perform sfinae):

#include <iostream>
template <typename T>
struct A {
void mem_func() {
std::cout << T::template static_func<int>() << '\n';
}
template <typename K>
static constexpr bool static_func() { return true; }

template <typename T1, typename TT = T>
typename std::enable_if<TT::template static_func<T1>(), void>::type sfinae_func() {
std::cout << "This fails" <<'\n';
}
};

struct B : A<B> {
};
int main() {
B a;
a.mem_func();
a.sfinae_func<int>();
}

[live demo]

Using a nested name specifier in CRTP

impl_t is an incomplete type in base. You can solve the problem either using another template parameter or using a type traits technique:

template<class>
struct traits;

template<>
struct traits<deriv_t>
{
static constexpr std::size_t i_q = 1;
};

...

void print () {
std::cout << "Base, i_q = " << traits<impl_t>::i_q << std::endl;
}

using vec_t = std::array<double, traits<impl_t>::i_q>;

You don't have complaints in print() because at the point of its instantiation impl_t becomes a complete type.

Can a concept be checked against incomplete type

You can check a concept against an incomplete type - but if that concept check requires actually doing anything with the type that requires it to complete, that's going to fail the concept check.

And even there, you have to be careful since implementations are allowed to (and will) cache concept checks to compile faster - so if you try C<T> while T is incomplete and try again when T becomes complete, and those should give different answers, you're asking for trouble.

Foo isn't complete at the point you're checking it, so naturally it doesn't have a member named a. This really cannot possibly work.

because this prevents concepts for being used together with some well-known patterns such as CRTP.

Being used together in this way, yes. In the same way that with CRTP you also can't access anything directly off of the template parameter passed into the base class, you always have to be careful to delay any instantiation of that type until it is complete.

This is ultimately the same reason:

template <typename Derived>
struct B {
typename Derived::type x;
};

struct D : B<D> {
using type = int;
};

does not work.

Is it possible to access a member inside the parent class body with CRTP?

No, I am afraid that is not possible. When A<B> is used as the base class, it must be instantiated (since a base class must be complete), but at that point B is still an incomplete type and thus its members cannot be accessed inside A<B>.



Related Topics



Leave a reply



Submit