C++模板特化优先级。模板特化

3

这个主题看起来有点混乱,但我不知道如何更好地表达它,抱歉=)

让我们看一下以下代码:

#include <iostream>

template<typename T>
void f(T value) {
  std::cout << "f<T>" << std::endl;
}

template<>
void f(int value) {
  std::cout << "f<int>" << std::endl;
}

template<typename T>
struct S {
  using type = T;
};

template<typename T>
void f(typename S<T>::type value) {
  std::cout << "f<S<T>>" << std::endl;
};

int main() {
  f(123);
  f<int>(123);
}

输出结果为:
$ ./testgcc 
f<int>
f<S<T>>

那么问题是为什么第一次调用会导致 f<int> 的特化,而第二次使用显式的 int 模板参数则导致调用“有模板的”f<S<int>>()?在标准中是否有规定如何在这种情况下实例化模板?
提前感谢!
PS:在不同版本的gcc和clang上进行了测试-行为相同。我没有Windows系统来测试MSVC,但我在godbolt上测试了MSVC在以下代码中的结果:
_main   PROC
        ; ....
        push    123                           ; 0000007bH
        call    void f<int>(int)                      ; f<int>
        add     esp, 4
        push    123                           ; 0000007bH
        call    void f<int>(int)                      ; f<int>
        ; ...

因此,无论哪种情况下 MSVC 都会调用 f<int>。这种行为是否被记录为“实现定义”?


1
这在这里有详细介绍。这两者在功能上是等效的,因为“对于任何给定的模板参数集,这两个表达式的求值结果相同”。然后稍后又说:“如果程序包含了函数模板的声明,它们在功能上是等效的但不是等同的,那么该程序是非法的;不需要进行诊断。” - Mike Lui
如果 int 不应该使用 S<T> 版本,那么哪些类型应该使用呢?你如何决定? - Jerry Jeremiah
"这种行为在文档中被定义为实现定义吗?" 不是的,这要么是编译器的错误,要么是不合法的程序NDR或UB。 - Jarod42
2个回答

3

您这里有未定义行为。

与类不同,模板函数无法进行部分特化。像您编写的那样,模板函数可以进行完全特化。

template<>
void f(int value) {
  std::cout << "f<int>" << std::endl;
}

但是,“部分特化”往往会导致未定义行为,因为它被视为不相关的声明。
template<typename T>
void f(T value) {
  std::cout << "f<T>" << std::endl;
}

template<typename T>
void f(typename S<T>::type value) {
  std::cout << "f<S<T>>" << std::endl;
};
// These two are conflicting declarations for "f" and compiler has no way to disambiguate.
// Worse due to nature of templates it tends to fail to figure out that there is ambiguity. 
// How to differentiate the two anyways?

使用SFINAE来明确声明每个f的声明与哪些类型名称相关,以避免冲突。

@JerryJeremiah SFINAE 是一些简单的东西,但它有非常不友好的语法。 思路是添加额外的 typename 作为条件,该条件是从其他条件中确定的 - 如果 typename 最终被证明是无法编译的胡言乱语,则声明将无效并被忽略。 你最好去在线指南上寻找关于编写 SFINEA 代码的指导 - 这个主题有指南和视频。 - ALX23z
如果你的意思是要与帖子中完全相同的情况,那么它有点本质上没有意义。但是,如果你只是想要一些不是畸形的东西,那么就在类型名T上想出一些任意条件A,并在A为真时启用第一个声明,在A为假时启用第二个声明。 - ALX23z
这两个重载 template<typename T> get()template <std::size_t I> get() 具有相同的名称和输入参数(无)。OP 没有部分特化,只是添加了一个重载。 - Jarod42
@Jarod42 我的错,在模板情况下,重载不仅涉及输入参数,还涉及模板参数。get()可以通过模板参数进行区分,他的f函数声明无论是模板参数还是输入参数都无法区分。从他的帖子中我理解到,他对模板特化感兴趣 - 而不是更基础的重载。 - ALX23z
这不是重新定义,(在我看来甚至不是UB/非法形式)。请参见我的答案。 - Jarod42
显示剩余9条评论

0

让我们从简单的情况开始:f(123);

template<typename T> void f(typename S<T>::type)中,T无法被推导。

因此只有一个可行的函数f<T>(T),其中T=int

我们选择f<T>(T)(选择仅在主模板中发生),这将解析为专门化的f<int>(int)

第二种情况:f<int>(123);

现在,两个函数都是可行的:

  • template<typename T> void f(typename S<T>::type),其中T=int
  • f<T>(T),其中T=int

第一个比第二个更具体,因此选择第一个。

注意:
Gcc、Clang和Mscv都同意Demo


其他顺序会得到相同的结果这里。consexpr应避免UB,仅保留ill formed程序NDR。 - Jarod42
@ALX23z:在泛型模板之前,您不能编写(完整的)特化。 您的版本无法编译(不是因为“static_assert”)。 - Jarod42
它在MSVC上无法工作,因为它会创建链接器错误。这两个函数都生成相同的符号,导致UB。此外,如果f(123)结果是一个数字,但f<int>(123)的结果是另一个数字,那么这已经是一个巨大的问题了。在VS2019中,如果同时使用这两个函数,则它们都返回0,但如果只有一个存在,则它们的结果要么是0,要么是3。 - ALX23z
好的,确实存在一个问题,但对我来说,这是一个msvc的bug。它以相同的方式命名了两个不同的实体。特别是在添加constexpr后,可以解决这个问题。 - Jarod42
这不是MSVC的错误。这两个声明都应该生成符号T f<T>(T);,这是一种ODR违规 - 为什么gcc和clang会生成T f(T);超出了我的理解。问题是:你如何使函数指针指向它们? - ALX23z
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接