web-dev-qa-db-ja.com

サードパーティのヘッダーのみのライブラリで「externテンプレート」を使用する

私は glm library を使用しています。これは、3Dグラフィックスを対象としたヘッダーのみの数学ユーティリティのコレクションです。 Clangおよび ClangBuildAnalyzer-ftime-traceを使用すると、glmタイプのインスタンス化に多くの時間が費やされていることに気づきました。

**** Templates that took longest to instantiate:
 16872 ms: glm::vec<4, signed char, glm::packed_highp> (78 times, avg 216 ms)
 15675 ms: glm::vec<4, unsigned char, glm::packed_highp> (78 times, avg 200 ms)
 15578 ms: glm::vec<4, float, glm::packed_highp> (78 times, avg 199 ms)

...

そこで、glmのラッパーヘッダーとソースのペアを作成し、extern templateを使用して不要なインスタンス化を回避することにしました。

// glmwrapper.h

#pragma once

#include <glm.hpp>

extern template struct glm::vec<4, signed char, glm::packed_highp>;
extern template struct glm::vec<4, unsigned char, glm::packed_highp>;
extern template struct glm::vec<4, float, glm::packed_highp>;
// glmwrapper.cpp

template struct glm::vec<4, signed char, glm::packed_highp>;
template struct glm::vec<4, unsigned char, glm::packed_highp>;
template struct glm::vec<4, float, glm::packed_highp>;

今、私のプロジェクトでは、<glm.hpp>を含める代わりに、代わりに"glmwrapper.h"を含めています。残念ながら、それは何も変更しませんでした。 -ftime-traceおよび ClangBuildAnalyzer を再び使用すると、同じ数のインスタンス化が報告されます。また、測定可能なコンパイル時間の違いはありません。

Isuspectこれは、#include <glm.hpp>が実際にテンプレート定義を含むことになり、その時点で後続のextern template宣言が冗長になるためです。

glmライブラリを変更せずに、私が望むものを達成する方法はありますか?


疑似コードでは、私はちょっとこのようなものが欲しい:

// glmwrapper.h (psuedocode)

#pragma once

#include <glm.hpp>

// Make definition of the templates unavailable:
undefine template struct glm::vec<4, signed char, glm::packed_highp>;
undefine template struct glm::vec<4, unsigned char, glm::packed_highp>;
undefine template struct glm::vec<4, float, glm::packed_highp>;

// Make declaration of the templates available:
extern template struct glm::vec<4, signed char, glm::packed_highp>;
extern template struct glm::vec<4, unsigned char, glm::packed_highp>;
extern template struct glm::vec<4, float, glm::packed_highp>;
// glmwrapper.cpp (psuedocode)

// Define templates only in the `.cpp`, not in the header:
template struct glm::vec<4, signed char, glm::packed_highp>;
template struct glm::vec<4, unsigned char, glm::packed_highp>;
template struct glm::vec<4, float, glm::packed_highp>;
9
Vittorio Romeo

残念ながら、これらのインスタンス化を回避する方法はありません。 classテンプレートの明示的なインスタンス化宣言は、そのテンプレートのprevent(暗黙の)インスタンス化を行いません。他の変換ユニットが実際の関数シンボルとオブジェクトコードを提供するため、非インライン、非テンプレートメンバー関数(多くの場合、それらのどれでもないことが多い)のインスタンス化を防ぐだけです。

テンプレート定義を確認することでインスタンス化が発生するわけではありません(どのスペシャライゼーションがインスタンス化されますか?)。その理由は、クラスが完全であることを必要とするコードは、そのlayoutおよびメンバー関数宣言(オーバーロードの解決のため)を知る必要があり、一般にクラスのインスタンス化が不足していることを知る方法はありません:

template<class T> struct A : T::B {
  typename std::conditional<sizeof(T)<8,long,short>::type first;
  typename T::X second;
  A() noexcept(T::y)=default;  // perhaps deleted
  using T::B::foo;
  void foo(T);
  // and so on…
};

void f() {A<C> a; a.foo(a.first);}  // …maybe?

この「透明性」は、テンプレート化されたエンティティの 他のいくつかの種類 にも拡張されます。compilationがテンプレートの定義を必要とする場合、linkerは無関係です。

良い知らせは、C++ 20のmodulesが次のような状況で役立つはずだということです:明示的なインスタンス化definitionモジュールインターフェイスでは、典型的な実装で、インスタンス化されたクラス定義を残りのモジュールインターフェイスデータと共にキャッシュし、翻訳単位のインポートでの解析とインスタンス化の両方を回避します。モジュールは、クラスで定義されたクラスメンバーおよびフレンドの暗黙のinlineも削除します(これは長い間意味がありませんでした)。明示的なインスタンス化宣言は、暗黙的なインスタンス化を防ぎます。

2
Davis Herring