Consider this code
template <typename T>
struct delay : std::false_type{};
template <typename T>
struct my_typelist {
static_assert(delay<T>{}, "");
};
template <typename Tuple>
struct test;
template <typename T>
struct test<my_typelist<T>> {
void pass(){}
};
template <typename T>
void fail(const test<T> &){}
int main()
{
test<my_typelist<int>> t;
t.pass();
fail(t);
}
Without calling fail()
the code compiles and runs fine. However, using t
in any function seems to trigger the static_assert
in the my_typelist
class, even though the class is never instantiated. Though the example is contrived, I ran into the same issue using incomplete types inside a std::tuple
, even though I simply used std::tuple
as a type list and never instantiated it.
Why does the static_assert
only trigger once I use the variable as a parameter, and not when I call a member function? In what contexts is my_typelist
instantiated and when is it not?
Note that I would have used variadic template but the error occurs regardless of this so I opted in to using the lowest common denominator.
In the case of fail(t)
, if my_typelist<int>
declares a friend function named fail
in its class definition, that function will be found by argument-dependent lookup (because my_typelist<int>
is an associated class of test<my_typelist<int>>
). In certain cases, that friend, instead of the global function fail
, may get selected by overload resolution (demo). Therefore, the definition of my_typelist<int>
must be instantiated and examined to see whether that happens. Adding parentheses around fail
will suppress argument-dependent lookup and remove the need to instantiate my_typelist<int>
, and in that case, static_assert
is not triggered (demo).
In the case of t.pass()
, my_typelist<int>
is not instantiated because it is known that t.pass()
will always call a member function of test<my_typelist<int>>
, and that won't be affected by the completeness of my_typelist<int>
.
So, let us go this way:
First change your static_assert
from this working code:
template <typename T>
struct my_typelist {
static_assert(delay<T>{}, "");
};
to this:
template <typename T>
struct my_typelist {
static_assert(false, "");
};
It is going to fire immediately. Note that the false
expression doesn't depend on any type.
Now, change it to a delay<T>
type that doesn't depend on any template parameter, say, char
, int
, etc:
template <typename T>
struct my_typelist {
static_assert(delay<int>{}, "");
};
It is still going to fire immediately.
So what's happening here?
class templates are not implicitly instantiated even when used, unless its used in contexts that requires completely defined types.
temp.inst/1
Unless a class template specialization has been explicitly instantiated ([temp.explicit]) or explicitly specialized ([temp.expl.spec]), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program.
See, @cpplearner's answer to understand why calling the function fail(...)
instantiates my_typelist<int>
. Basically ADL kicks in which forces such instantiation, you can suppress using a qualified name ::foo
or parenthesis.
For completeness: In one of the rules among others for ADL (emphasis mine):
basic.lookup.argdep/2: For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered.
....
basic.lookup.argdep/2.2: If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of its associated classes. Furthermore, if T is a class template specialization, its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces of which any template template arguments are members; and the classes of which any member templates used as template template arguments are members. [ Note: Non-type template arguments do not contribute to the set of associated namespaces. — end note ]
....
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