関数テンプレートパラメータのC ++テンプレートのインスタンス化
質問
テンプレートのインスタンス化[*]を使用すると、次の問題が発生します。
ファイル 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<boost::_bi::bind_t...>
関数 Foo::func
をインスタンス化するにはどうすればよいですか?関数を引数として取るため、少し混乱しています。通常の非関数型で慣れているように、 foo.cc にインスタンス化関数を追加しようとしました:
instantiate()
{
template<> void Foo::func<boost::function<void(int)> >(boost::function<void(int)>);
}
明らかに、これは機能しません。誰かが私を正しい方向に向けてくれれば幸いです。
ありがとう!
[*]はい、parashift FAQ liteを読みました。
解決
これに対する答えはコンパイラに依存します。 Sun C ++コンパイラの一部のバージョンは、個別の翻訳ユニット間で共有されるテンプレート関数実装のキャッシュを構築することにより、これを自動的に処理します。
Visual C ++、およびこれを実行できない他のコンパイラを使用している場合は、ヘッダーに関数定義を含めることもできます。
ヘッダーが複数の.ccファイルに含まれている場合、重複した定義について心配する必要はありません。コンパイラは、テンプレート生成メソッドに特別な属性を付けて、リンカが文句を言うのではなく重複を破棄することを認識します。これが、C ++に<!> quot; one definition rule <!> quot;がある理由の1つです。
編集:上記のコメントは、テンプレートが任意のタイプパラメーターをリンクできる必要がある一般的な場合に適用されます。クライアントが使用する型の閉じたセットがわかっている場合は、テンプレートの実装ファイルで明示的なインスタンス化を使用することにより、それらが使用可能であることを確認できます。ただし、テンプレートがクライアントのみが知っている可能性のある型で動作する必要がある一般的な場合、テンプレートをヘッダーファイルと実装ファイルに分けることはほとんど意味がありません。とにかく、クライアントは両方の部分を含める必要があります。クライアントを複雑な依存関係から分離する場合は、それらの依存関係をテンプレート化されていない関数の背後に隠し、テンプレートコードから呼び出します。
他のヒント
必要に応じてファイルに分割します:
私がこれをお勧めするわけではありません。それが可能であることを示すだけです。
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
}
foo.ccをcaller.ccに含めていますか。インスタンス化はコンパイル時に発生します。コンパイラが呼び出し元で呼び出しを検出すると、テンプレートのインスタンス化されたバージョンを作成しますが、完全な定義を使用可能にする必要があります。
どちらも言及しているのは、テンプレート関数の定義(宣言だけでなく)を使用するファイルに含める必要があるということです。テンプレート関数は、使用しない限り、実際には存在しません。それらを別のccファイルに入れると、そのccファイルをヘッダーファイルまたはそれらを呼び出しているファイルに明示的に#include
しない限り、コンパイラは他のccファイルでそれらについて知りませんパーサーは動作します。
(だからこそ、Earwickerが説明したように、テンプレート関数の定義は通常ヘッダーファイルに保持されます。)
明確なものはありますか
Earwickerは正しいと思います。この場合、テンプレートメンバ関数funcを明示的にインスタンス化する際の問題は、boost :: bindによって返される型が実装に依存することです。 boost :: functionではありません 。 boost :: functionはboost:bindを含むことができます。これは、右側の型(boost :: bindの結果)を推定するテンプレート割り当て演算子があるためです。この特定のboostの実装でのcaller.ccでのこのfuncの特定の使用では、boost :: bindのタイプは、実際には<!> lt;および<!> gt; (つまり、boost::_bi::bind_t...
)。ただし、そのタイプのfuncを明示的にインスタンス化すると、移植性の問題が発生する可能性があります。