[英]dllexport class template instances (specializations), reducing compilation time for header-only template libraries
是否可以導出一些類模板實例,同時讓庫的用戶能夠生成給定類模板的其他特化(在編譯可執行文件時)。
鑒於我有一個公共標題
// public.h
#pragma once
#ifndef DLL_BUILD
#define API __declspec(dllimport)
#else
#define API __declspec(dllexport)
#endif // !DLL_BUILD
#include <type_traits>
// dummy to generate .lib
struct API dummy
{
void be_dummy();
};
template <class T>
struct Foo
{
static T Sum(T a, T b)
{
static_assert(std::is_fundamental_v<T>);
return a + b;
}
};
通過這種聲明類模板Foo
的方式,每個實例化都將在用戶的可執行文件中發生。
但是,如果我使用API
宏將Foo
定義為dllexport/dllimport
,則沒有在 dll 中明確實例化的Foo
的每個特化都將無法鏈接。
// impl.cpp - dll
#include "public.h"
void dummy::be_dummy()
{
volatile int a = 0;
return;
}
template API struct Foo<int>;
///////////////////////////////////////////
// main.cpp - executable
#include "public.h"
#include <iostream>
int main()
{
dummy().be_dummy();
// std::cout << Foo<double>().Sum(4.12, 3.18) << std::endl; // Unresolved external symbol
std::cout << Foo<int>().Sum(60, 9) << std::endl; // executed within the dll
return 0;
}
因此,是否有可能強制編譯器在一個已導出的情況下鏈接到現有的類模板實例,並生成另一個尚未導出的類模板實例。
更新
我找到了解決方案,請參閱下面的答案。 我保留舊的更新以防萬一有人會發現 SFINAE 的這種用法有幫助。
更新舊的
我找到了一個涉及 SFINAE 的繁瑣解決方案,但它會導致定義一個類模板兩次,因此非常容易出錯。 我不知道它是否可以用宏以一種可以只寫一次的方式包裝起來。
// public.h
#pragma once
#ifndef DLL_BUILD
#define API __declspec(dllimport)
#else
#define API __declspec(dllexport)
#endif // !DLL_BUILD
#include <type_traits>
namespace templ_export
{
template <class T>
struct is_exported : std::false_type {};
// this can be placed to a separated header (i.e. Exported.hpp)
template <> struct is_exported<int> : std::true_type {};
template <class T>
struct API FooExported
{
static T Sum(T a, T b)
{
//static_assert(std::is_fundamental_v<T>);
return a + b;
}
};
template <class T>
struct FooNotExported
{
static T Sum(T a, T b)
{
//static_assert(std::is_fundamental_v<T>);
return a + b;
}
};
template <class T, bool = templ_export::is_exported<T>()>
struct GetFooExported
{
using type = FooNotExported<T>;
};
template <class T>
struct GetFooExported<T, true>
{
using type = FooExported<T>;
};
}
template <class T>
using Foo = typename templ_export::GetFooExported<T>::type;
/////////////////////////////////
// impl.cpp
#include "public.h"
void dummy::be_dummy()
{
volatile int a = 0;
return;
}
template struct API templ_export::FooExported<int>;
這是導出類模板實例的簡單方法。
在創建 Dll 時,編譯器必須認為Foo
被定義為 dllexport。 但是在創建可執行文件並鏈接到該 Dll 時, Foo
類模板不得應用任何declspec
屬性。 盡管我們需要將特定的類模板實例聲明為dllimport
。
// public.h
#pragma once
#ifndef DLL_BUILD
#define API __declspec(dllimport)
#else
#define API __declspec(dllexport)
#endif // !DLL_BUILD
// define T_API emplty for library users, hence they will see just 'struct Foo'
#ifndef T_API
#define T_API
#endif
#include <type_traits>
template <class T>
struct T_API Foo
{
static T Sum(T a, T b)
{
//static_assert(std::is_fundamental_v<T>);
return a + b;
}
};
// impl.cpp
// Compile with DLL_BUILD defined
// define T_API for library build
#define T_API __declspec(dllexport)
#include "public.h"
void dummy::be_dummy()
{
volatile int a = 0;
return;
}
// instantiating class template
template struct T_API Foo<int>;
對於可執行文件:
// Exported.h
// this header needs to be shipped alongside with public.h and included after
#pragma once
// declare template instance as imported
template struct __declspec(dllimport) Foo<int>;
// main.cpp
// Executable linked to library
#include "public.h"
#include "Exported.h"
int main()
{
dummy().be_dummy();
// Sum is called from Executable
std::cout << Foo<double>().Sum(4.12, 3.18) << std::endl;
// Sum is called from Dll
std::cout << Foo<int>().Sum(60, 9) << std::endl;
return 0;
}
我認為這種方法對僅包含標頭的模板庫很有用。 與預編譯頭文件一樣,帶有類模板實例的 dll 將減少編譯時間。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.