Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial template specialization triggering static_asserts

Tags:

c++

c++11

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.

like image 362
user975989 Avatar asked Jan 17 '17 05:01

user975989


2 Answers

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>.

like image 55
cpplearner Avatar answered Nov 10 '22 11:11

cpplearner


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 ]

  • ....

like image 7
WhiZTiM Avatar answered Nov 10 '22 11:11

WhiZTiM