使用Clang

问题描述

我有这三个文件

// foo.h
#pragma once

template <typename T> const T foo;

template <>
const int foo<int> = 1;
// a.cpp
#include "foo.h"

int main() {}
// b.cpp
#include "foo.h"

如果我在Linux上使用GCC 7.5.0构建这些文件,则可以正常工作:

$ g++ -std=c++17 a.cpp b.cpp -o out
$

但是在Mac上使用Apple Clang 12.0.0时,会出现此错误:

$ clang++ -std=c++17 a.cpp b.cpp -o out
duplicate symbol 'foo<int>' in:
    /var/folders/g5/8twmk1xj481_6btvppyw5j4h0000gp/T/a-62bdde.o
    /var/folders/g5/8twmk1xj481_6btvppyw5j4h0000gp/T/b-ea4997.o
ld: 1 duplicate symbol for architecture x86_64

这是错误还是Clang中的错误?

解决方法

它违反了ODR,应为inline(自C ++ 17起)或位于未命名的namespace中(自C ++ 11起)。

为什么违反ODR:

在整个程序(包括任何标准库和用户定义库)中,都需要出现每个非内联函数或变量的唯一且仅一个定义(请参见下文)。不需要编译器来诊断这种违反情况,但是未定义违反该行为的程序的行为。

由于foo.ha.cpp都包含b.cpp,因此在每个翻译单元foo<int>a.cpp中都定义了变量b.cpp 。因此该程序中有2个定义,这违反了以前的规则,除非它是未使用的(这意味着不是 ODR使用的)或它是一个inline变量。

您有2种解决方案:

  1. template<> int const foo<int> = 1;放入另一个foo.cpp。然后它只有一个定义。
  2. inline添加foo(自C ++ 17起)。 inline变量被允许在翻译单元中具有多个相同定义。如果不是在C ++ 17中,则可以将其放入未命名的namespace中,这意味着将其中的所有内容都设为inline
,

RedFrog的答案是正确的-这违反了“单一定义规则”(ODR),但并没有真正解释为什么违反了ODR。在C ++中,全局const变量具有内部(static)链接,这将导致每个编译单元具有单独的变量实例,因此不会违反ODR。但是it turns out认为const不会导致 template 变量具有内部链接:

未声明为extern的非局部非易失性非模板(自C ++ 14起)非内联(自C ++ 17起)声明时使用的const限定符为其提供内部链接。 / p>

有几种方法可以解决此问题。

使用static的内部链接

您可以在模板变量上使用staticgives them internal linkage

在命名空间范围内声明的以下任何名称都有内部链接:

变量,变量模板(自C ++ 14起),函数或声明为静态的函数模板;

编辑:我不建议您使用此技术。如果将static应用于声明和专业化,则它在Clang中可以正常工作,但GCC(至少到目前为止)抱怨

error: explicit template specialization cannot have a storage class

如果仅在声明中设置static,则它会在GCC中编译,但是使用Clang会出现重复的符号错误。

使用namespace的内部链接

将模板变量放在匿名名称空间中也会为其提供内部链接。

此外,在未命名的命名空间或未命名的命名空间中的命名空间中声明的所有名称,甚至是明确声明为extern的名称,都具有内部链接。

使用inline禁用ODR

这有点不同。声明为inline但未声明static的模板变量仍然具有外部链接,但具有are allowed to have more than one definition

具有外部链接(例如,未声明为静态)的内联函数或变量(自C ++ 17起)具有以下附加属性:

  • 程序中的内联函数或变量(自C ++ 17起)可能有多个定义,只要每个定义出现在不同的翻译单元中(对于非静态内联函数和变量(自C起) ++ 17))所有定义都是相同的。例如,可以在包含在多个源文件中的头文件中定义内联函数或内联变量(自C ++ 17起)。
  • 必须在每个翻译单元中内联声明。
  • 每个翻译单位的地址都相同。

这意味着变量的inline的行为类似于函数的inline

如有疑问,inline可能是最佳选择。在非常简单的测试中,我完成了所有操作,即使在-O0时也都产生了相同的输出,但是理论上inline保证程序中变量只有一个副本,因为它仍然具有外部链接,而其他方法没有。也许。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...