0

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>;
Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28

1 Answers1

0

Here is a simple way of exporting class template instances.

On Dll creation compiler must think, that Foo is defined as dllexport. But while creating an Executable and linking to that Dll, Foo class template must not have any declspec attributes applied to it. Though we need to declare particular class template instances as 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>;

For executable:

// 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;
}

I consider this approach to be useful for header-only template libraries. Like precompiled headers, dll with class template instances will reduce compilation time.

Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28