c++ - 哪个编译器在模板方法专业化方面是正确的?
问题描述
我没有跟上模板的复杂性,所以只知道足够危险。但是,我遇到了一个问题(由我们的一位测试人员报告),代码在 Xcode(Clang)和 Visual Studio 2019 上正确编译,但行为不同,我想知道哪个是“正确的”
考虑以下简单类:
template <class T>
class foo
{
void set(int index, T value);
};
该默认模板化设置方法对于大多数类型T的值都适用,但我需要专门针对特定类型的行为,将其称为 String。
所以在相关的 CPP 文件中,我添加了以下内容:
template <>
void foo<String>::set(int index, String value)
{
// code here
};
在 Xcode 下,这工作得非常好。使用String的第二个参数调用set正确调用了该专用模板。
但是,使用 Visual Studio 编译失败的完全相同的代码表现不同。使用String参数的同一个调用只是调用了原始集而不是专用版本。
我假设在 Xcode 下,编译器正确地创建了一个调用,其签名与 CPP 文件中可用的专用版本相匹配,因此被正确链接。
所以我的问题是:
- 为什么这在 Xcode 中有效但在 Visual Studio 中无效
- 哪个编译器行为正确,该问题是否应该被视为另一个编译器中的错误?
顺便说一句,我确实尝试在头文件中创建一个显式签名:
template<>
void foo<String>::set(int index, String value );
并且 Xcode 没有抱怨它,一切仍然有效。但是,Visual Studio 虽然没有抱怨该标头签名,但确实在链接时抱怨它找不到匹配的实现,因此:
- 为什么那行不通?
虽然我确实找到了一种适用于两种编译器的方法(基本上将内联版本放在头文件中,但我真的很想了解为什么会发生这个问题。
我提前感谢人们。
编辑:吹是我刚刚放在一起的一个真实例子。这在 Xcode 下编译并运行得非常好(特别是对变量 g 正确调用了字符串专业化),但无法在 Visual Studio 2019 中链接并出现以下错误
( public: void __cdecl Foo<class std::basic_string<char,struct std::char_traits,class std::allocator >>::print(int,class std::basic_string<char,struct std::char_traits,class std ::allocator >)" (?print@?$Foo@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@@QEAAXHV?$basic_string@DU ?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) 在函数 main 中引用)在函数 main 中引用)
Classes.h
---------
#pragma once
#include <iostream>
#include <string>
template <class T>
class Foo
{
public:
void print(int index, T value)
{
std::cout << "Using implicit template\n";
}
};
Classes.cpp
-----------
#include "Classes.h"
template<>
void Foo<std::string>::print(int i, std::string xyz)
{
std::cout << "Using explicit String template\n";
}
Main.cpp
--------
#include <string>
#include <iostream>
#include "Sub/Classes.h"
//==============================================================================
int main (int argc, char* argv[])
{
Foo<double> f;
f.print(1, 2.0);
Foo<std::string> g;
g.print(1, "abc");
return 0;
}
解决方案
您需要在.h
文件中转发声明完整的专业化,以告诉其他翻译单元不要实例化通用模板。
template <typename T> struct foo {
void set(int, T);
};
template <>
void foo<String>::set(int, String);
如果你不这样做,你就违反了单一定义规则。发生这种情况是因为在编译其他.cpp 文件时,编译器不知道存在完整的特化,并实例化通用模板。
说在bar.cpp
你写foo<String>().set(1, {});
。编译器根据通用模板的定义构造该函数,并将其发送到.o
or.obj
文件中。在链接时,链接器会看到多个版本的foo<String>::set
. 它假设它们是等效的并随机选择一个。你一般不知道它会选择哪个。更糟糕的是,一些调用set
可能是内联的,并且调用的版本可能与链接器选择的版本不同。这就是 ODR 违规如此危险的原因。
将前向声明template <> void foo<String>::set(int, String);
放在标题中告诉所有翻译单元“不要自动实例化此模板,我保证将此定义放在某个 .cpp 文件中。” 现在bar.cpp
不实例化模板。它调用foo<String>::set
任何其他前向声明的函数。foo.cpp
提供了该完整专业化的定义,并且在任何地方都可以使用。
推荐阅读
- mysql - Teradata 到 mysql 的转换 - 资格和排名
- pyspark - 如何从 SparkSession 对象创建 DataFrame 以读取 PNG 文件格式?
- python - Python我怎样才能让所有if导致else
- java - 使用自签名证书时出现 SSLHandshakeException 和 CertPathValidatorException 错误
- freemarker - 在地图中的地图上迭代
- javascript - 为引导程序制作自定义网格结构?
- postgresql - 如何在 Postgres 中存储社交媒体 Unix 时间戳,保留用户的本地时间
- scala - Gatling - 如何在 Scala 中设置 Gatling 控制台日志级别
- java - 在 call() 方法的返回语句执行之前具有对象引用的未来对象
- javascript - TinyMCE 在编辑器中显示错误