函数模板:外部模板与显式专用化

Function templates: extern template vs explicit specialisation

本文关键字:专用 外部 函数模板      更新时间:2023-10-16

请考虑以下函数模板声明:

template <typename T, typename = std::enable_if_t<std::is_same_v<T, int>>>
void foo(T i);

此模板只有一个可能的有效实例化,即T = int.我想把这个定义放在一个实现文件中。我可以想到两种可能的方法。(如果你想知道为什么我会这样做,而不仅仅是说void foo(int i),这是因为模板版本阻止了调用站点的隐式转换。

方法1:

我可以使用extern template声明来告诉其他 TU,foo<int>()已在其他地方实例化:

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

方法2:

我可以为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

问题:

  • 这两种方法都是合法的,还是我无意中依赖 UB?
  • 如果两者都是合法的,除了方法2的打字性略少之外,是否有充分的理由偏爱一种方法而不是另一种方法?GCC 和 Clang 在这两种方法中都同样有效。
  • 对于方法 2,是否确实需要明确的专业化声明(*)?同样,如果省略它,GCC 和 Clang 都非常高兴,但这样做让我感到不舒服,因为在另一个 TU 中调用foo(3)是一个模板的隐式实例化,没有可见的定义,也没有承诺这样的定义存在于其他地方。

还有第三种方法。 在方法 3 中,指定要拥有的函数,并添加模板重载并将其标记为delete。 那看起来像

void foo(int i)
{
// stuff
}
template <typename T>
void foo(T t) = delete;

由于模板版本将完全匹配所有类型,因此在除int以外的所有情况下都将首选,因为非模板完全匹配优于模板版本。 因此,您只能使用int调用foo,而所有其他类型都会给您一个错误,指出他们正在尝试调用已删除的函数void foo(T t)

现场示例