8

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 to foo(3) in another TU is an implicit instantiation of a template with no definition visible, and no promise that such a definition exists elsewhere.
Tristan Brindle
  • 16,281
  • 4
  • 39
  • 82

1 Answers1

6

There is a third approach. In approach 3 you specify the function you want to have and the you add a template overload and mark that as delete. That looks like

void foo(int i)
{
    // stuff
}

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

Since the template version will match all types exactly it will be preferred in all cases except int since a non template exact match is preferred to a template one. So you will only be able to call foo with an int and all other types will give you an error that they are trying to call the deleted function void foo(T t).

Live Example

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • isn't the code ill formed because of [temp.res/8.1](http://eel.is/c++draft/temp#res-8.1)? – W.F. Mar 30 '17 at 19:38
  • 1
    @W.F. As far as I know it is not for that reason and according to [this](http://stackoverflow.com/questions/32716589/force-function-to-be-called-only-with-specific-types) a compiler error is the expected result. – NathanOliver Mar 30 '17 at 19:40