Is it possible to export some of the class template instances, while leaving to a library's user the ability of generating other specializations of a given class template (when compiling the executable).
Given I have a public header
// 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;
}
};
With this way of declaring class template Foo every instantiation will happen inside the user's executable.
However, if I define Foo as dllexport/dllimport using API macro, every specialization of Foo which has not been explicitly instantiated inside the dll, will fail to link.
// 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;
}
So, is it possible to force the compiler to link against an existing class template instance when one has been exported, and to generate another that has not.
UPDATE
I found a solution, see my answer below. I leave the old update just in case anybody will find such usage of SFINAE helpful.
UPDATE OLD
I found one cumbersome solution involving SFINAE, but it results in defining a class template twice, therefore is very error prone. I don't know if it can be wrapped up with macro in a manner that will make it possible to write it only once.
// 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>;
Here is a simple way of exporting class template instances.
On Dll creation compiler must think, that
Foois defined as dllexport. But while creating an Executable and linking to that Dll,Fooclass template must not have anydeclspecattributes applied to it. Though we need to declare particular class template instances asdllimport.For executable:
I consider this approach to be useful for header-only template libraries. Like precompiled headers, dll with class template instances will reduce compilation time.