Consider the following function template declaration:
template <typename T, typename = std::enable_if_t<std::is_same_v<T, int>>>
void foo(T i);
There is only one possible valid instantiation of this template, namely with T = int
. I'd like to put this definition in an implementation file. I can think of two possible ways of doing so. (If you're wondering why on earth I'd do this rather than just saying void foo(int i)
, it's because the template version prevents implicit conversions at the call site.)
Approach 1:
I can use an extern template
declaration to tell other TUs that foo<int>()
is instantiated elsewhere:
// In foo.hpp
template <typename T, typename = std::enable_if_t<std::is_same_v<T, int>>>
void foo(T i);
extern template void foo(int);
// In foo.cpp
template <typename T, typename>
void foo(T i) { ... } // full template definition
template void foo(int); // explicit instantiation with T = int
Approach 2:
I can provide an explicit specialisation for the int
case:
// In foo.hpp
template <typename T, typename = std::enable_if_t<std::is_same_v<T, int>>>
void foo(T i);
template <> void foo(int i); // explicit specialisation declaration (*)
// In foo.cpp
template <>
void foo(int i) { ... } // explicit specialisation definition
Questions:
- Are both of these approaches legal, or am I unintentionally relying on UB?
- If both are legal, is there a good reason to prefer one approach over the other, other than the fact that approach 2 is very slightly less typing? Both GCC and Clang work equally well with either approach.
- For approach 2, is the explicit specialisation declaration at
(*)
actually required? Again, both GCC and Clang are perfectly happy if it's omitted, but doing so makes me uncomfortable about the fact that a call tofoo(3)
in another TU is an implicit instantiation of a template with no definition visible, and no promise that such a definition exists elsewhere.