The following code gives different output with and without the line marked with *
commented:
#include <iostream>
#include <type_traits>
template <bool>
using bool_void_t = void;
template <typename, typename = void>
struct is_complete : std::false_type
{
};
template <typename T>
struct is_complete<T, bool_void_t<sizeof(T) == sizeof(T)>> : std::true_type
{
};
template <typename Derived>
struct Base
{
static constexpr bool value = is_complete<Derived>{};
// using magic = bool_void_t<value>; // *
};
struct Foo : Base<Foo>
{
};
int main()
{
std::cout << std::boolalpha << Foo::value << std::endl;
}
*
is commented: true
.*
is not commented: false
.In both cases clang++ 5.0.0 is used as a compiler and the compiler flags are -std=c++17 -Wall -Wextra -Werror -pedantic-errors
.
clang++ 5.0.0 (-Wall -Wextra -Werror -pedantic-errors
)
-std=c++14 -std=c++17
* is commented false true
* is not commented false false
g++ 7.2.1 (-Wall -Wextra -Werror -pedantic-errors
)
-std=c++14 -std=c++17
* is commented true true
* is not commented false false
*
is commented (the output is false
with the -std=c++14
compiler flag and true
with the -std=c++17
one)?A common problem encountered when using CRTP is that when the base class is instantiated, the derived class is not complete. This means you cannot use member typedefs in the derived class, among other things.
This makes sense when you think about it: a template class is really a way to generate a new class type based on the given template types, so until the compiler reaches the closing }
(in an approximate sense), the base class isn't fully defined. If the base class isn't fully defined, then obviously the derived class can't be either.
Because both the base and derived class are empty (in the first example), the compiler considers them complete. I would say this is incorrect, but I'm not an expect and can't be sure. Still, the trick here is that you are instantiating the value of is_complete
while defining the base class. After the derived class is fully defined, it will be complete.
Also, for an example of where this matters, consider something like this:
template <typename>
class crtp_traits;
class derived;
template <>
class crtp_traits<derived>
{
public:
using return_type = int;
};
template <typename T>
class base
{
public:
auto get_value() const -> typename crtp_traits<T>::return_type
{
return static_cast<T const*>(this)->do_get_value();
}
};
class derived : public base<derived>
{
public:
auto do_get_value() const -> int
{
return 0;
}
};
The simple solution of giving derived
a member typedef using return_type = int;
will not work because derived won't be complete by the time base tries to access the typedef.
Turning out my comment into answer:
I think that is_complete
make the code ill-formed (No diagnostic required)...
Its value might depends where it is instantiated and breaks ODR.
class A; // A not complete yet
static_assert(!is_complete<A>::value);
class A{}; // Now it is
static_assert(is_complete<A>::value);
From dependent_name
If the meaning of a non-dependent name changes between the definition context and the point of instantiation of a specialization of the template, the program is ill-formed, no diagnostic required. This is possible in the following situations:
- a type used in a non-dependent name is incomplete at the point of definition but complete at the point of instantiation.
That seems to be the case for magic
.
Even when magic
is commented, following should also make code ill formed:
static constexpr bool value = is_complete<Derived>{};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With