为什么模板不能在extern "C"块内?

29

这是对一个答案的跟进问题,该答案回答了是否可能在模板内typedef指向extern "C"函数的指针类型?

这段代码无法通过g++、Visual C/C++和Comeau C/C++的编译,错误信息基本相同:

#include <cstdlib>

extern "C" {
    static int do_stuff(int) {
        return 3;
    }

    template <typename return_t_, typename arg1_t_>
    struct test {
        static void foo(return_t_ (*)(arg1_t_)) { }
    };
}

int main()
{
    test<int, int>::foo(&do_stuff);
    return EXIT_SUCCESS;
}

g++报错"error: template with C linkage",Visual C/C++编译器会发出编译器错误C2894,而Comeau C/C++则会报错"error: this declaration may not have extern "C" linkage"。

问题是,所有编译器都支持以下写法:

#include <cstdlib>

extern "C" {
    static int do_stuff(int) {
        return 3;
    }

    struct test {
        static void foo(int (*)(int)) { }
    };
}

int main()
{
    test::foo(&do_stuff);
    return EXIT_SUCCESS;
}

C++标准的第7.5节“链接规范”指出:

对于类成员的名称和类成员函数的成员函数类型,忽略C语言链接。

它甚至给出了一个例子:

extern "C" {
    class X {
        void mf(); // the name of the function mf and the member
                // function's type have C++ language linkage
        void mf2(void(*)()); // the name of the function mf2 has C++ language
                // linkage; the parameter has type pointer to C function
    };
}

如果在extern "C"块中允许使用模板,则实例化的成员函数将具有C ++链接。那么,为什么C++98标准的第14章“Templates”会声明如下:一个模板名称可能具有链接(3.5)。模板、显式模板特化(14.7.3)和类模板部分特化不得具有C链接。模板名称可能有链接是什么意思?什么是模板链接?为什么明确禁止具有C链接的模板是可以的,而模板实例化的所有成员函数(默认构造函数、析构函数和赋值操作符重载)都将具有C++链接?

1
啊,你找到了禁止模板具有 C 链接的条款! - Fred Nurk
6个回答

18

模板不是实际的代码,它们只是编译器为了在模板参数已知时如何生成代码而提供的指导方针。因此,在尝试使用它们之前,它们实际上并不存在。你不能为不存在的东西提供链接。


1
为什么C++标准明确允许在extern "C"块内声明类? - Daniel Trebbien
3
@Daniel,类是具体而明确的。虽然某些方法可能无法从 C 中访问,但数据成员仍可能有用。 - Mark Ransom
2
数据成员不会被编码,也没有调用约定。标准明确表示extern "C"不适用于它们。我不明白在这里从C中使用它们有什么关系。 - Fred Nurk
2
C语言链接对所有类成员都被忽略,而不仅仅是成员函数。标准实际上给出了两个例子,我引用了第二个例子。第一个例子展示了一个静态变量和一个成员变量,并声明两者都具有C++语言链接。 - Daniel Trebbien

12

模板“可能”具有链接性意味着什么?什么是模板链接性?

所有名称都具有外部链接性、内部链接性或无链接性(C++03 §3.5p2),但这与语言链接性不同。(很令人困惑,我知道。C++0x 在链接性方面也做了相当大的改变。)任何用作模板参数的内容都需要具有外部链接性:

void f() {
  struct S {};
  vector<S> v;  // Not allowed as S has internal linkage.
}

请注意,C++98在您引用的§14p4中使用了"may",但C++03将其删除,因为模板不能在会给它们内部链接的上下文中声明:

void f() {
  // Not allowed:
  template<class T>
  struct S {};
}

6

因为extern C禁用了名称重整,而模板使用了名称重整

要查看模板是如何实现名称重整的,请编译和反编译:

#include <cassert>

template <class C>
C f(C i) { return i; }

int main() {
    f<int>(1);
    f<double>(1.5);
}

使用:

g++ -c -g -std=c++98 main.cpp
objdump -Sr main.o

输出结果包括:
int main() {
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
    f<int>(1);
   8:   bf 01 00 00 00          mov    $0x1,%edi
   d:   e8 00 00 00 00          callq  12 <main+0x12>
            e: R_X86_64_PC32    _Z1fIiET_S0_-0x4
    f<double>(1.5);
  12:   48 b8 00 00 00 00 00    movabs $0x3ff8000000000000,%rax
  19:   00 f8 3f 
  1c:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  20:   f2 0f 10 45 f8          movsd  -0x8(%rbp),%xmm0
  25:   e8 00 00 00 00          callq  2a <main+0x2a>
            26: R_X86_64_PC32   _Z1fIdET_S0_-0x4
}
  2a:   b8 00 00 00 00          mov    $0x0,%eax
  2f:   c9                      leaveq 
  30:   c3                      retq

请注意所有的callq都被调用到一些奇怪的名字,比如_Z1fIiET_S0_

其他基于名称改编的特性(例如函数重载)也是如此。

在这个链接中,我写了一个更加详细的答案。


3

由于模板函数名需要添加额外的信息,extern "C" 可以关闭修饰。 extern "C" 的目的是能够声明可以使用 C 连接的函数,这显然对模板函数永远不起作用。


1
标准中引用的例子表明,一个类可以在 extern "C" 块中声明,并且它的成员函数是使用 C++ 链接声明的。如果允许模板,则其成员函数也将具有 C++ 链接。这就是我认为你所指的“附加信息”。我猜我不明白其中的区别是什么。 - Daniel Trebbien
@Daniel,在类名上并不需要使用修饰符,但模板名需要,否则编译器如何区分template<char>template<wchar_t> - Mark Ransom

0

由于标准规定:

在确定类成员的语言链接时,将忽略 C 语言链接。

因此,所有编译器似乎存在一个错误。


-4
因为在 C 语言中没有模板。

12
C语言中也没有类,但是允许在extern "C"块中出现类的声明和定义。为什么呢? - Daniel Trebbien
1
没有勺子。 - Ciro Santilli OurBigBook.com

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