90364


visual
studio2022を用いて、静的ライブラリを生成するプロジェクト(ProjectLib)とそのlibを参照する実行ファイルのプロジェクト(ProjectExe)を開発しています。

**ProjectLibは以下のファイルを含みます。**

projectlib.h

[code]

    #pragma once
    template<typename T>
    void func(T t);
    
[/code]

projectlib.cpp

[code]

    #include "projectlib.h"
    #include <iostream>
    #include <variant>
    
    template<typename T>
    void func(T t) {
      std::cout << t;
    }
    
    struct Instantiate {
      void  operator()() {
          std::variant<int, std::string> v;
          std::visit([](auto v) { func(v); }, v);
      }
    };
    
[/code]

上記のファイルではテンプレート関数funcに対してstruct Instantiateを用いてint,
std::string型のインスタンス化をしています。

**ProjectExe**  
main.cpp

[code]

    #include "lib.h"
    #pragma comment(lib, "ProjectLib.lib")
    
    int main()
    { 
      int i = 10;
      std::string s = "test";
      func(i);
      func(s);
    }
    
[/code]

main.cppではProjectLib.libをリンクしてインスタンス化したint, std::string型のfuncを使用します。

**問題**  
上記の書き方の場合、vc++では正常にビルドできるのですが、clangを使用したときにProjectExeビルド時にfuncのインスタンスのシンボルを解決できずにリンクエラーになることです。

**回避策1**  
回避策として、Instantiateの宣言をprojectlib.hに記述して、void
Instantiate::operator()()の定義をprojectlib.cppに記述ことですが、それは避けたい回避策です。

**回避策2**  
明示的インスタンス化を使用して、各型に対応するインスタンスを生成することもできますが、今後型が増えるたびにメンテナンスする必要があるので、この回避策も避けたいです。  
template void func(int t);

インスターフェースを汚さずにcppファイル内でインスタンス化する方法はありますでしょうか？

～～～～～最終コード～～～～

[code]

    // projectlib.h
    class Func
    {
    private:
    struct Instantiate;
    
    public:
    template <typename T>
    static void func(T t);
    }
    
[/code]

[code]

    // projectlib.cpp
    template <typename T>
    void Func::func(T t) { ... }
    
    struct Func::Instantiate
    {
      void operator()();
    }
    
    void Func::Instantiate::operator()() 
    {
      // if(false) {
      std::variant<int, std::string> v;
      std::visit([](auto v) { func(v); }, v);
      //} //if(false)で囲った場合は実体化されない
    }
    
[/code]


CC BY-SA 4.0
2022-08-03T03:36:51.183
90355
2022-08-08T00:54:44.680
2022-08-08T00:54:44.680
53827
53827
question
c++ visual-c++ clang
静的ライブラリプロジェクトのテンプレート関数の明示的インスタンス化できません。


ご存じのことも含まれるとは思いますが、まず前提として

  * テンプレートはその翻訳単位で使っていれば実体化される (ポインタだけの利用など、実体が必要なければ実体化されない)
  * 複数の翻訳単位に同一の実体があればリンクしたときにひとつに統合される
  * `extern template` で指定することで実体化は抑制できる

というのが基本的なルールです。

この場合には質問者は `Instantiate::operator()` の中で `func<int>` と `func<std::string>`
を使っていますから、この実体は既に存在していることになり、 `main.cpp` 内で `extern template`
で指定すればこの翻訳単位で実体化を抑制しても問題ありません。

そしてもうひとつの重要なルールはクラス定義内でのメンバ関数定義は暗黙に `inline` 指定されたものとみなすということです。 つまり、この場合には
`Instantiate::operator()`
にインライン指定が付いているわけですが、この関数自体が呼び出されていないので実体も作られず、故にそこから呼ばれる関数テンプレートも実体化されていないのだと考えられます。

解決するためにはクラス定義の外で `Instantiate::operator()` を定義すれば良いでしょう。

これらを統合してソースコードで表すと

[code]

    // main.cpp
    #include <string>
    #include "lib.h"
    
    extern template void func<int>(int);
    extern template void func<std::string>(std::string);
    
    int main()
    { 
      int i = 10;
      std::string s = "test";
      func<int>(i);
      func<std::string>(s);
    }
    
[/code]

[code]

    // projectlib.cpp
    #include <iostream>
    #include <variant>
    #include <string>
    #include "lib.h"
    
    template<typename T>
    void func(T t) {
      std::cout << t;
    }
    
    struct Instantiate {
      void operator()(void);
    };
    
    void Instantiate::operator()(void) {
        std::variant<int, std::string> v;
        std::visit([](auto v) { func(v); }, v);
    }
    
[/code]

[code]

    // lib.h
    #pragma once
    
    template<typename T>
    void func(T t);
    
[/code]

というような要領です。


CC BY-SA 4.0
2022-08-03T07:40:25.483
90364
2022-08-03T07:40:25.483
3364
90355
answer