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
How Can Duff's Device Code Be Compiled
Eigen::Ref for Concatenating Matrices
Understanding Gsl::Narrow Implementation
When Does a Std::Vector Reallocate Its Memory Array
Programmatically Check Whether My MAChine Has Internet Access or Not
How to Invoke a User-Defined Conversion Function via List-Initialization
How to Make Sure That Std::Random_Shuffle Always Produces a Different Result
Vc2013: Function from Bind Not Compiling
Displacement Map Filter in Opencv
Why C++11 Compiler Support Still Requires a Flag
How to Pass a Vector to Execvp
C++11: "Narrowing Conversion Inside { }" with Modulus
C++ Get Handle of Open Sockets of a Program
What Is the Status on Dynarrays
Is It a Good Practice to Always Use Smart Pointers
How to #Include When There Is a Circular Dependency