18

I am attempting to add some extern template into my project, to speed up build times and reduce footprint on disk during build.

I've done so by listing specialisations that I use frequently, e.g.

extern template class std::vector<std::string>;
extern template class std::vector<unsigned int>;
extern template class std::vector<uint32_t>;
extern template class std::vector<size_t>;

The problem is that unsigned int is size_t, maybe, and uint32_t is unsigned int, maybe. So, depending on the build target, compilation fails for the "non-extern" variety of the list that actually instantiates the specialisations:

template class std::vector<std::string>;
template class std::vector<unsigned int>;
template class std::vector<uint32_t>;
template class std::vector<size_t>;

The error is like this (line and column number inaccurate):

templates-impl.h:15:36: error: duplicate explicit instantiation of ‘class std::vector<long unsigned int>’ [-fpermissive]
 template class std::vector<size_t>;

My goal is to just be able to list the types I use, as I use them, with a minimum of fuss and with no need to hardcode variants of the list for different targets.

I guess in C++14 I could solve this with an if constexpr and some std::is_same, at least if that were permitted at namespace scope.

How can I do it in C++11?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055

4 Answers4

7

Alternatively, you may have type-lists of to-be-instantiated types, of which only repeating types are replaced with dummy types (hopefully, to be optimized-out at link time).

In C++11, something like:

template<typename... V>
struct explicit_instlist
{
    template<int I>
    struct get
    {
        using VI = typename std::tuple_element<I,std::tuple<V...>>::type;

        using type = typename std::conditional< is_first_at<I,VI,V...>::value,
            VI, dummy_inst<I,VI> >::type;
    };
};

template<unsigned I>
using my_list = typename explicit_instlist<
    std::string,
    unsigned int,
    uint32_t,
    size_t
    >::template get<I>::type;

/*extern*/ template class std::vector< my_list<0> >;
/*extern*/ template class std::vector< my_list<1> >;
/*extern*/ template class std::vector< my_list<2> >;
/*extern*/ template class std::vector< my_list<3> >;

where dummy_inst<I,T> generates a unique dummy type to be used in place of T when already used, and is_first_at works like:

template<int I,typename T>
struct dummy_inst {};

template<int I,typename T,typename... V>
struct is_first_at {};

template<int I,typename T,typename V,typename... Vs>
struct is_first_at<I,T,V,Vs...>: std::conditional< std::is_same<T,V>::value,
    std::integral_constant<bool,I==0>,
    is_first_at<I-1,T,Vs...> >::type {};

clearly, dummy_inst will need to be specialized if the to be explictly instantiated template does not support the empty default. Something like boost preprocessor may be used to avoid explictly 'iterating' the explicit instantations and write a macro accepting the type list directly ...

Massimiliano Janes
  • 5,524
  • 1
  • 10
  • 22
6

How about drop the unsigned int and size_t specializations and just make them all size-based?

extern template class std::vector<uint8_t>;
extern template class std::vector<uint16_t>;
extern template class std::vector<uint32_t>;
extern template class std::vector<uint64_t>;
SoronelHaetir
  • 14,104
  • 1
  • 12
  • 23
1

Here's a way of doing it:

#include <utility>
#include <type_traits>

template<typename T>
struct Test {};

template<typename... Ts>
struct types {};

template<template<typename>class Template, typename Types>
struct Instantiate {};

template<template<typename>class Template, typename T0, typename... Ts>
struct Instantiate<Template, types<T0, Ts...>>:
  Instantiate<Template, types<Ts...>>
{
  Template<T0>& unused();
};

template<typename U, typename Types>
struct prepend;

template<typename U, template<typename...>class pack, typename... Ts>
struct prepend< U, pack<Ts...> > {
  typedef pack<U, Ts...> types;
};
template<typename U, typename Types>
using Prepend = typename prepend<U, Types>::types;

template<typename U, typename Types, typename=void>
struct remove_type_from_types;
template<typename U, template<typename...>class pack>
struct remove_type_from_types<U, pack<>, void>
{
  typedef pack<> types;
};

template<typename U, template<typename...>class pack, typename T0, typename... Ts>
struct remove_type_from_types< U, pack<T0, Ts...>,
  typename std::enable_if< std::is_same<U, T0>::value >::type
>: remove_type_from_types< U, pack<Ts...> >
{};

template<typename U, template<typename...>class pack, typename T0, typename... Ts>
struct remove_type_from_types< U, pack<T0, Ts...>,
  typename std::enable_if< !std::is_same<U, T0>::value >::type
>
{
  typedef Prepend< T0, typename remove_type_from_types< U, pack<Ts...> >::types > types;
};

template<typename Types>
struct remove_duplicates {
  typedef Types types;
};

template<template<typename...>class pack, typename T0, typename... Ts>
struct remove_duplicates<pack<T0, Ts...>> {
private:
  typedef typename remove_type_from_types< T0, pack<Ts...> >::types filtered_tail;
  typedef typename remove_duplicates< filtered_tail >::types unique_tail;
public:
  typedef Prepend< T0, unique_tail > types;
};
template<typename Types>
using RemoveDuplicates = typename remove_duplicates<Types>::types;

static Instantiate<Test, RemoveDuplicates<types<int, double>> > unused;

Possible improvements/alternatives:

  • Do a daisy chain call. Then call the bottom one in an exported function that is not called by anyone. The templates will be instantiated in order to export that function, and later at linking the exported function will be eliminated

  • Do a sum-of-sizeof

1

I am not sure how extern works but if it is a question of explicitly instantiating the template, maybe you can use a function invocation like this. Type duplication shouldn't generate an error.

template<class T>
void instance(T const&){std::vector<T> v{}; (void)v;}

void instantiate(){
   instance(std::string{}):
   instance(unsigned int{});
   instance(uint32_t{});
   instance(size_t{});   
}

(I am not saying it is the best method.)

alfC
  • 14,261
  • 4
  • 67
  • 118
  • 1
    It may work but the standard states that an explicit template definition must be provided ([\[temp.explicit\]/11](https://timsong-cpp.github.io/cppwp/n3337/temp.explicit#11)). Moreover, for a class it will be cumbersome since you will need to use each non-template members. – Oliv Dec 24 '17 at 10:47
  • @Oliv, thanks for the note, this is way out of my league. – alfC Dec 24 '17 at 11:18