Function templates: extern pattern and explicit specialization

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 valid instantiation of this template, namely T = int

. I would like to put this definition in an implementation file. I can imagine two possible ways to do this. (If you're wondering why on earth I would do this rather than just say void foo(int i)

, it's because the template version prevents implicit conversions in the calling site.)

Approach 1:

I can use a declaration extern template

to tell other TUs what foo<int>()

is being created 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 specialization for the case int


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



  • Are both of these approaches legitimate or am I inadvertently relying on UB?
  • If both are legal, is there a good reason to prefer one approach over the other other than that approach 2 prints very slightly less? Both GCC and Clang work equally well with either approach.
  • Approach 2 does it require an explicit declaration of specialization in (*)

    ? Again, both GCC and Clang are perfectly happy to omit it, but it makes me awkward that a call foo(3)

    in another TU is an implicit template instance with no visible definition and doesn't promise that such a definition exists elsewhere.

source to share

1 answer

There is a third approach. In Approach 3, you specify the function you want to have and add a template overload and mark it as delete

. It looks like

void foo(int i)
    // stuff

template <typename T>
void foo(T t) = delete;


Since the template version will match all types exactly, this is preferred in all cases, except int

because an exact template match is preferred over the template. This way you will only be able to call foo

with help int

and all other types will give you an error that they are trying to call the remote function void foo(T t)




All Articles