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

Posted

技术标签:

【中文标题】函数模板参数的C++模板实例化【英文标题】:C++ template instantiation of function template parameters 【发布时间】:2010-09-18 16:29:51 【问题描述】:

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

文件 foo.h

class Foo

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

private:
    int member_;
;

文件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&lt;boost::_bi::bind_t...&gt;

如何实例化 函数 Foo::func?因为它需要一个函数作为参数,所以我有点困惑。我尝试在 foo.cc 中添加一个实例化函数,因为我习惯于使用常规的 non-function 类型:

instantiate()

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

显然,这不起作用。如果有人能指出我正确的方向,我将不胜感激。

谢谢!

[*] 是的,我阅读了 parashift FAQ 精简版。

【问题讨论】:

强制实例化:template void Foo::func(myFunc f); 【参考方案1】:

根据需要将其拆分为文件: 不是我推荐这个。只是表明这是可能的。

扑通.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";
;

扑通.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) 中,它说它是“未知类型”,对我来说,这意味着不应使用具体类型(如上所示),并且可以在任何给定时间更改类型(打破上面的代码)。【参考方案2】:

这个问题的答案取决于编译器。某些版本的 Sun C++ 编译器会通过构建模板函数实现的缓存来自动处理这个问题,这些实现将在不同的翻译单元之间共享。

如果您使用的是 Visual C++,以及任何其他无法做到这一点的编译器,您不妨将函数定义放在头文件中。

如果标头包含在多个 .cc 文件中,不必担心重复定义。编译器使用特殊属性标记模板生成的方法,以便链接器知道丢弃重复项而不是抱怨。这也是 C++ 具有“单一定义规则”的原因之一。

编辑: 上述 cmets 适用于您的模板必须能够链接给定任何类型参数的一般情况。如果您知道客户端将使用的一组封闭类型,则可以通过在模板的实现文件中使用显式实例化来确保它们可用,这将导致编译器生成其他文件链接的定义。但是在一般情况下,您的模板需要使用可能只有客户端知道的类型,那么将模板分为头文件和实现文件就没有什么意义了;无论如何,任何客户都需要包括这两个部分。如果您想将客户端与复杂的依赖项隔离开来,请将这些依赖项隐藏在非模板化函数后面,然后从模板代码中调用它们。

【讨论】:

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

我相信 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 系统上都做过很多次他正在做的事情,没有任何问题。 我坚持我的声明。看看 Martin York 的帖子中的“knarly typedef”。您无法将 myfunc 替换为 boost::function 并使其正常工作。 @Head Geek:这个答案是关于 bind 返回的“未知类型” - 这意味着几件事,首先它不是 boost::function,然后它可以是由库实现者随时更改,因为它不是公共接口的一部分——它们不一定会在下一个 boost 版本中返回相同的类型。【参考方案4】:

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

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

有更清楚的吗?

【讨论】:

实际上你可以强制实例化而不使用它。请参阅下面我将代码拆分为三个文件并在不使用它的情况下实例化。 一个常见的错误是假设在大多数情况下模板都在头文件中定义,它必须如此。 如果您计划在多个 .cpp 文件中使用它们,您必须在头文件中定义它们。我敢肯定该规则有一些难以理解的例外,但这是一个有充分理由的规则。【参考方案5】:

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

【讨论】:

... 或者当你显式实例化它时。

以上是关于函数模板参数的C++模板实例化的主要内容,如果未能解决你的问题,请参考以下文章

C++模板详解:泛型编程模板原理非类型模板参数模板特化分离编译

C++模板详解:泛型编程模板原理非类型模板参数模板特化分离编译

C++ 使用指向相同函数的指针作为模板参数是不是总是会导致相同的实例化?

C++从青铜到王者第六篇:C++模板初阶

C++泛型编程

C++泛型编程