I haven't written many C templates till recently, but I'm trying to take a deep dive into them. Fixing one developer's code I managed to produce some compiler behavior that I can't explain. Here is the simplified program. ( When I try to simplify more, I lose that "strange" behavior). Let's think of some class Depender, which is a parameter of template struct Dependency. Depender
can depend on the List of Dependee
s. I have some macros that can produce specializations of the Dependency
template. Dots in the following code block stand for the possible macro expansion. I have a forward-declared MainDependee before dots, and MainDependee's definition after dots.
#include <type_traits>
template <typename T, typename = void>
struct IsCompleteType : std::false_type
{};
template <typename T>
struct IsCompleteType<T, std::enable_if_t<( sizeof( T ) > 0 )>> : std::true_type
{};
template<template<typename...> class List, typename Dependee>
struct DependencyInternal
{
using Type = std::conditional_t<IsCompleteType<Dependee>::value, Dependee, Dependee>;
};
template<typename... Ts> class StubList;
class MainDependee; // forward declaration
class MainDepender
{};
template<template<typename...> class List, typename Depender>
struct Dependency;
....... //here is the specialization
class MainDependee {};
int main()
{
Dependency<StubList, MainDepender> a;
}
When the dots are replaced with
template<template<typename... Us> class List>
struct Dependency<List, MainDepender>
{
using Type = typename DependencyInternal<List, MainDependee>::Type;
};
then in main
I get IsCompleteType<MainDependee>::value == true
.
But when the dots are replaced with
template<>
struct Dependency<StubList, MainDepender>
{
using Type = typename DependencyInternal<StubList, MainDependee>::Type;
};
then IsCompleteType<MainDependee>::value == false
.
Please tell me, what rule describes the difference between these options?
CodePudding user response:
An explicit (full) specialization is not itself a templated entity and all name lookup etc., as well as ODR, is done for it as if it wasn't a template. MainDependee
is not complete at the point you wrote it and therefore IsCompleteType<MainDependee>
is inherited from std::false_type
at this point.
A partial specialization is itself a templated entity and will follow the rules for templates. In particular a definition will be instantiated from the partial specialization only when and where an instantiation is required. In this case the instantiation for Dependency<StubList, MainDepender>
has its point of instantiation directly before main
where MainDependee
is complete. Only there Type
is computed (because the right-hand side is dependent on the template parameter) and IsCompleteType<MainDependee>
instantiated, so that it will be inherited from std::true_type
.
Note that class template specializations are instantiated only once per translation unit. If you use Dependency<StubList, MainDepender>::value
multiple times in a translation unit, it will always result in the same value, the one which would be correct at the first point from where instantiation is required.
Furthermore, if Dependency<StubList, MainDepender>::value
or IsCompleteType<MainDependee>
would have different values in different translation units because of different relative placement of MainDependee
, your program will be IFNDR (ill-formed, no diagnostic required), effectively meaning it will have undefined behavior.
It is not possible to use a type trait like this to safely check whether a type is complete. For the same reason there is no such trait in the standard library.