C++函数模板参数的模板实例化

6

我在使用模板实例化时遇到了以下问题 [*]。

文件名为 foo.h

class Foo
{
public:
    template <typename F>
    void func(F f)

private:
    int member_;
};

file foo.cc

template <typename F>
Foo::func(F f)
{
     f(member_);
}

文件caller.cc

Foo::func(boost::bind(&Bar::bar_func, bar_instance, _1));

虽然这个编译没有问题,但是链接器报告了一个未定义的符号:

void Foo::func<boost::_bi::bind_t...>

我该如何实例化函数Foo::func?由于它需要一个函数作为参数,我有点困惑。我尝试像常规的非函数类型一样在foo.cc中添加一个实例化函数:

instantiate()
{
    template<> void Foo::func<boost::function<void(int)> >(boost::function<void(int)>);
}

显然,这样行不通。如果有人能指点一下我正确的方向,我将不胜感激。
谢谢!
[*] 是的,我已经阅读了parashift FAQ lite。

使用以下方式强制实例化:template void Foo::func<myFunc>(myFunc f); - Martin York
5个回答

5
答案取决于编译器。一些版本的Sun C++编译器会自动处理这个问题,通过构建一个模板函数实现的缓存,该缓存将在独立的翻译单元之间共享。
如果您使用的是Visual C++和其他不能执行此操作的编译器,则最好将函数定义放在头文件中。
如果头文件被多个.cc文件包含,不要担心重复定义。编译器使用特殊属性标记模板生成的方法,以便链接器知道丢弃重复项而不是抱怨。这是C++具有“一个定义规则”的原因之一。
编辑:上述评论适用于您的模板必须能够在给定任何类型参数的情况下链接的一般情况。如果您知道客户端将使用的一组封闭类型,则可以通过在模板的实现文件中使用显式实例化来确保它们可用,这将导致编译器为其他文件生成可链接的定义。但是,在您的模板需要与可能仅为客户端所知的类型一起工作的一般情况下,将模板分离为头文件和实现文件没有什么意义;任何客户端都需要同时包含两个部分。如果要隔离客户端免受复杂依赖关系的影响,请将这些依赖关系隐藏在非模板函数背后,然后从模板代码中调用它们。

在编辑后,boost::bind的结果是一个“未定义”的类型,因此在这种情况下显式模板实例化不是一个好的解决方案(您可以阅读bind.hpp实现并确定模板实例化的真实类型,但随着类型不是接口的一部分,对绑定库的更新可能会破坏它)。 - David Rodríguez - dribeas

3

将它分成文件,就像你想要的那样:
并不是我推荐这样做。只是想表明这是可能的。

plop.h

#include <iostream>
class Foo
{
public:
    Foo(): member_(15){}


    // Note No definition of this in a header file.
    // It is defined in plop.cpp and a single instantiation forced
    // Without actually using it.
    template <typename F>
    void func(F f);

private:
    int member_;
};


struct Bar
{
     void bar_func(int val) { std::cout << val << "\n"; }
};

struct Tar
{
    void tar_func(int val) { std::cout << "This should not print because of specialisation of func\n";}
};

Plop.cpp

#include "plop.h"
#include <boost/bind.hpp>
#include <iostream>

template <typename F>
void Foo::func(F f)
{
     f(member_);
}

// Gnarly typedef
typedef boost::_bi::bind_t<void, boost::_mfi::mf1<void, Bar, int>, boost::_bi::list2<boost::_bi::value<Bar>, boost::arg<1> (*)()> > myFunc;

// Force the compiler to generate an instantiation of Foo::func()
template void Foo::func<myFunc>(myFunc f);

// Note this is not a specialization as that requires the <> after template.
// See main.cpp for an example of specialization.

main.cpp

#include "plop.h"
#include <boost/bind.hpp>
#include <iostream>

// Gnarly typedef
typedef boost::_bi::bind_t<void, boost::_mfi::mf1<void, Tar, int>, boost::_bi::list2<boost::_bi::value<Tar>, boost::arg<1> (*)()> > myTar;

// Specialization of Foo::func()
template<> void Foo::func<myTar>(myTar f)
{
    std::cout << "Special\n";
}
// Note. This is not instantiated unless it is used.
// But because it is used in main() we get a version.

int main(int argc,char* argv[])
{
    Foo f;
    Bar b;
    Tar t;

    f.func(boost::bind(&Bar::bar_func, b, _1)); // Uses instantiation from plop.cpp
    f.func(boost::bind(&Tar::tar_func, t, _1)); // Uses local specialization
}

我认为这种解决方案的问题是boost :: bind返回的具体类型不属于它们的公共接口(文档)。在文档(boost :: bind)中,它说它是一个“未知类型”,对我来说,这意味着不应使用具体类型(如上所示),而且类型可以随时更改(破坏上面的代码)。 - David Rodríguez - dribeas

1
你是否将foo.cc包含到caller.cc中?实例化是在编译时发生的——当编译器看到caller中的调用时,它会生成模板的实例化版本,但需要有完整的定义可用。

...或者当您显式地实例化它时。 - David Rodríguez - dribeas

1

我认为他们所指的是,模板函数的定义(不仅仅是声明)必须包含在使用它们的文件中。模板函数实际上不存在,除非它们被使用;如果您将它们放在单独的cc文件中,则编译器在其他cc文件中不知道它们,除非您明确地将该cc文件#include到头文件或调用它们的文件中,因为解析器的工作方式如此。

这就是为什么模板函数定义通常保存在头文件中,正如Earwicker所描述的那样。

清楚了吗?


1
实际上,您可以在不使用它的情况下强制进行实例化。请参见下面我将代码拆分为三个文件并在不使用它的情况下进行实例化的示例。 - Martin York
很常见的错误是认为大多数情况下模板是在头文件中定义的,所以一定是这样的。 - David Rodríguez - dribeas
如果您计划在多个 .cpp 文件中使用它们,必须在头文件中定义它们。我相信有一些艰难的例外情况,但这是一个有充分理由的规则。 - Head Geek

0

我相信Earwicker是正确的。在这种情况下,显式实例化模板成员函数func的问题在于boost::bind返回的类型取决于实现。它不是一个boost::function。boost::function可以包含boost:bind,因为它有一个模板赋值运算符,可以推断右侧(即boost::bind结果)的类型。在caller.cc中使用func时,在这个特定的boost实现中,boost::bind的类型实际上是在<和>之间提到的类型(即boost::_bi::bind_t...)。但是,为该类型显式实例化func可能会存在可移植性问题。


抱歉,但这不应该是个问题。我已经多次在Windows和Linux系统上做过他正在做的这种事情,没有遇到任何问题。 - Head Geek
1
我坚持我的说法。看看Martin York帖子中的“knarly typedef”。你无法用boost::function替换myfunc并使其正常工作。 - SCFrench
@Head Geek:这个答案是关于bind返回的“未知类型”的,它意味着几件事情,首先它不是boost::function<>,其次它可以随时被库实现者更改,因为它不是公共接口的一部分——他们不必在下一个boost版本中返回相同的类型。 - David Rodríguez - dribeas

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