可变参数模板的链接器错误

Cra*_*g P 3 c++ linker-errors variadic-templates

我有一个包含可变参数模板和辅助函数的程序:

#include <iostream>
#include <string>

using std::cout;

template<typename... Ts>
void fooImpl(char const *cp, Ts... args);

template<typename... Ts>
inline void foo(const std::string &s, Ts... args)
{
    fooImpl(s.c_str(), args...);
}

void fooImpl(char const *cp)
{
    // do something
}

template<typename T, typename... Ts>
void fooImpl(char const *cp, T val, Ts... args)
{
    char special{'@'};

    while (*cp)
    {
        if (*cp == special)
        {
            // handle val ...

            // recurse over remaining args
            fooImpl(cp, args...);
            return;
        }
        ++cp;
    }    
}

int main()
{
    std::string s = "Hello!";
    foo("Text", s, "C++", 3.14159, 42);
}
Run Code Online (Sandbox Code Playgroud)

这会产生链接器错误:

/tmp/ccZpPMC2.o:vt-test.cc:功能void foo<std::string, char const*, double, int>(std::string const&, std::string, char const*, double, int):错误:
未定义引用' void fooImpl<std::string, char const*, double, int>(char const*, std::string, char const*, double, int)'

如果我把之前的定义foo放下来,它编译并链接很好main().所以我认为它只是一个bug,但是我用GCC 4.9和clang 3.5得到了这个,所以也许我错过了什么?

Col*_*mbo 5

template<typename T, typename... Ts>
void fooImpl(char const *cp, T val, Ts... args) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

您正在声明原始模板的重载(!).

自从在通话中

fooImpl(s.c_str(), args...);
Run Code Online (Sandbox Code Playgroud)

在参数列表中有一个包扩展,unqualified-id表示一个从属名称1.依赖名称解析适用.[temp.dep.candidate]:

对于依赖于模板参数的函数调用,使用通常的查找规则(3.4.1,3.4.2,3.4.3)找到候选函数,除了:

  • 对于使用非限定名称查找(3.4.1)[..]的查找部分,仅查找模板定义上下文中的函数声明.

  • 对于使用关联命名空间(3.4.2)的查找部分,仅找到在模板定义上下文或模板实例化上下文中找到的函数声明.

可以肯定地说,非限定名称查找不会找到第二个重载,因为它只考虑模板定义上下文中的声明.

ADL确实适用于此处,但全局命名空间不会与参数包中的任何类型相关联.我们有std::string, char const*, double, int.[basic.lookup.argdep]/2指定:

  • 如果T是基本类型,则其关联的命名空间和类集都是空的.

  • 如果T是类类型(包括联合),则其关联的类是:类本身; 它所属的成员,如果有的话; 及其直接和间接基类.其关联的名称空间是其关联类是成员的名称空间.此外,如果T是类模板特化,则其关联的名称空间和类还包括:与模板类型参数(模板模板参数除外)提供的模板参数类型相关联的名称空间和类; 任何模板模板参数都是成员的名称空间; 以及用作模板模板参数的任何成员模板的类都是成员.

因此,基本类型也不std::string包括全局命名空间作为关联的命名空间.

长话短说...

...在ADL期间未搜索全局命名空间,并且未找到第二个重载.因此,函数模板的第一个重载是唯一找到并随后通过重载决策选择的重载.但是,未定义第一个重载,因此会发出链接器错误.


1) [temp.dep]/1:

在表达式中:

      后缀表达式 ( 表达式列表选择 )

其中postfix-expression是一个id-expression, id-expression表示依赖名称 if

  • 表达式列表中的任何表达式都是包扩展(14.5.3),
  • [..]