我无法从这些代码的不同编译器中获得一致的行为,并试图弄清楚我可能做错了什么。基本设置有一个模板类(Printer),一个部分特化(对于奇数值整数),它添加了一个静态const成员。当我尝试以各种不同的方式定义静态const成员时,会出现问题。
我已将代码缩减为三个文件。要回答一个直接的问题,我正在避免使用c ++ 11,所以我提供了一个自定义的enable_if模板。
档案“template_bug.h”
template<bool B, class T = void> struct my_enable_if {};
template< class T > struct my_enable_if<true, T> { typedef T type; };
template< int Value, typename = void > struct Printer {
static void doIt() { std::cout << "Printer<" << Value << "(even)>::doIt()" << std::endl; }
};
template< int Value > struct Printer< Value, typename my_enable_if<Value & 1>::type > {
static const char *c_prefix;
static void doIt() { std::cout << "Printer<" << c_prefix << Value << "(odd)>::doIt()" << std::endl; }
};
template<> const char *Printer<1>::c_prefix = "One_";
档案“other.cc”:
#include <iostream>
#include "template_bug.h"
template<> const char *Printer<5>::c_prefix = "Five_";
文件“main.cc”:
#include <iostream>
#include "template_bug.h"
template<> const char *Printer<3>::c_prefix = "Three_";
int main(void)
{
Printer<1>::doIt();
Printer<3>::doIt();
Printer<5>::doIt();
return 0;
}
现在,我的理解是这段代码应该编译并且不会调用任何未定义的行为,但我觉得我一定是错的。当我用g ++(4.8.4)编译它时:
g++ -c -o main.cc.o main.cc
g++ -c -o other.cc.o other.cc
g++ -o template_bug main.cc.o other.cc.o
我在链接阶段得到了这个:
other.cc.o:(.data+0x0): multiple definition of `Printer<1, void>::c_prefix'
main.cc.o:(.data+0x0): first defined here
我的印象是这个符号应该具有弱连接,以避免此错误。
问题1 :为什么Printer<1>::c_prefix
不是弱符号(在g ++下)?
如果我注释掉Printer<1>
用法,则其余的按预期编译,链接和执行。
在另一个编译器(Greenhills)上我得到的错误是:
[elxr] (error #412) unresolved symbols: 1
Printer<N1, my_enable_if<(bool)(N1&((int)1)), void>::type>::c_prefix [with N1=(int)5] from main.o
根本原因是编译器选择在两个编译单元之间生成不同的符号名称。使用nm我可以看到差异:
来自nm other.o:
00000000 D c_prefix__S__94Printer__ps__63_XZ1ZQ2_48my_enable_if__tm__28_XOcsb_1_Oad_2_Z1ZCiL_1_1OOv4type__tm__9_XCiL_1_5
来自nm main.o:
U c_prefix__94Printer__ps__63_XZ1ZQ2_48my_enable_if__tm__28_XOcsb_1_Oad_2_Z1ZCiL_1_1OOv4type__tm__9_XCiL_1_5
在与他们的支持交换了几封电子邮件后,我被告知我的代码正在调用UB,但他们解释为什么这是UB是没有意义的。
问题2 :有关Printer<5>
用法调用未定义行为的内容吗?
答案 0 :(得分:4)
问题2:有关
Printer<5>
用法调用未定义行为的内容吗?
必须在使用之前声明专业化,否则程序形成不良(无需诊断)。
所以正确的方法是:
文件&#34; template_bug.h&#34;
template<bool B, class T = void> struct my_enable_if {};
template< class T > struct my_enable_if<true, T> { typedef T type; };
template< int Value, typename = void > struct Printer {
static void doIt() {
std::cout << "Printer<" << Value << "(even)>::doIt()" << std::endl;
}
};
template< int Value > struct Printer< Value, typename my_enable_if<Value & 1>::type > {
static const char *c_prefix;
static void doIt() {
std::cout << "Printer<" << c_prefix << Value << "(odd)>::doIt()" << std::endl;
}
};
template<> const char *Printer<1>::c_prefix; // Declaration
template<> const char *Printer<3>::c_prefix; // Declaration
// or since C++17, inline static variable.
template<> inline const char *Printer<5>::c_prefix = "Five_";
在cpp中(可能会分成几个cpp,但只有一个定义):
template<> const char *Printer<1>::c_prefix = "One_"; // Definition
template<> const char *Printer<3>::c_prefix = "Three_"; // Definition
答案 1 :(得分:4)
请注意,One Definition Rule实际上有两种变体。
有些事情(大致是我们倾向于放在源文件中)可能只在整个程序中定义一次:
static
班级数据成员外部链接的其他内容(大致我们倾向于放在头文件中)可以在不同的翻译单元中多次定义,只要所有定义包含相同的标记并具有相同的含义:
static
类数据成员(在C ++ 17及更高版本中)class
,struct
,union
,enum
和typedef
/ using
定义(ODR都不适用于名称空间。)
在C ++ 14及更早版本中,static
类数据成员的每个显式特化都属于第一类。由于#include
基本上将头文件中的所有标记插入到翻译单元中,因此在标题中放置了明确的特化,因此两个翻译单元都是格式错误的,无需诊断。
因此,在C ++ 14或更早版本中,您需要将显式特化定义移动到源文件。但是,在类模板部分特化之后,您还应该尽快在头文件中声明所有显式特化:
template<> const char *Printer<1>::c_prefix;
template<> const char *Printer<3>::c_prefix;
template<> const char *Printer<5>::c_prefix;
但是,在C ++ 17及更高版本中,我们可以选择内联变量和内联static
类数据成员。因此,如果您出于某种原因希望在标题中保留显式特化定义,则可以将其内联:
template<> inline const char *Printer<1>::c_prefix = "One_";
(顺便说一句,您的代码可以更改c_prefix
指针以指向其他内容。如果您不想这样做,请考虑将类型从const char*
更改为{ {1}}。)
答案 2 :(得分:2)
我的印象是这个符号应该有微弱的联系 特别是为了避免这个错误。
你在哪里得到这种印象?
问题1:为什么打印机&lt; 1&gt; :: c_prefix不是弱符号(在g ++下)?
再次,为什么会这样?
现在,我的理解是这段代码应该编译而不是 调用任何未定义的行为
您违反了ODR。 Printer<1>::c_prefix
的定义应仅出现在一个编译单元中,因为它是一个完整的专业化。
template<> const char *Printer<1>::c_prefix = "One_";
您要么混淆概念(可能是与内联定义的弱联系),要么在某处产生误解。弱链接是ELF格式的实现 ABI详细信息。在gcc中,您需要明确地注释您想要具有弱链接的符号。您的代码与弱链接或弱符号无关。