10

I'm writing a C++ library that contains a lot of function templates I want to explicitly instantiate and export for several type parameters. In my particular case, I have a lot of numeric function templates that I want to separately instantiate and compile for float, double, and long double. They look something like this:

template <typename T>
T calculate_a(T x) { ... }

template <typename T>
T calculate_b(T x, T y) { ... }

// ...

If I have M function templates and N underlying types, then I have M*N explicit instantiations to type out. Is it possible to write these instantiations more concisely?

My current solution is to use a preprocessor macro that performs all instantiations for a given type:

#define EXPLICITLY_INSTANTIATE(T) \
    template T calculate_a<T>(T x); \
    template T calculate_b<T>(T x, T y); \
    // ...

EXPLICITLY_INSTANTIATE(float);
EXPLICITLY_INSTANTIATE(double);
EXPLICITLY_INSTANTIATE(long double);

However, this is suboptimal because it requires me to separately maintain another copy of each function template's signature. Also, if I want to do this in multiple translation units, then I need to separately maintain a list of underlying types in each. (Suppose that C++2a adds a long long double type that I want to support; I'll have to add EXPLICITLY_INSTANTIATE(long long double); to every file.)

Another possible approach is to gather up all of my functions into a (static-only) template class:

template <typename T>
class calculate {
    T a(T x) { ... }
    T b(T x, T y) { ... }
};

template class calculate<float>;
template class calculate<double>;
template class calculate<long double>;

This solves the first problem of separately maintaining two copies of each signature, but requires me to change each call of calculate_a into calculate::a<T>. It doesn't address the second problem.

David Zhang
  • 4,314
  • 1
  • 14
  • 18
  • 2
    If you have all floating-point types and all integral types for example, a SFINAE solution might be useful. – DeiDei May 14 '18 at 21:05
  • @DeiDei Could you elaborate? I'm unsure how to use SFINAE to write explicit instantiations. – David Zhang May 14 '18 at 21:08
  • 1
    @DeiDei the point is to not write explicit instantiations but to use SFINAE to only declare the template function for the types you care about. Another option is to `static_assert` if the template type is unsupported. For example: `template T calculate() { static_assert( std::is_floating_point::value, "Only supports floating points types" ); ... }` – clcto May 14 '18 at 21:12
  • @DavidZhang My comment was more of an alternative approach (like clcto mentions). There isn't really a way to easily generate an explicit instantiation for every built-in type in the language. You'll have to roll them one by one. The macro solution only saves some typing and is arguably less readable. – DeiDei May 14 '18 at 21:15
  • @clcto Ah, I haven't clearly communicated my intent in the question. The purpose of my explicit instantiations isn't to limit the types that these functions can be called with, but to inform the compiler to produce executable code for `float`, `double`, and `long double`. These functions are part of a library component that is compiled separately from the main program where they are used. I'll edit the question to clarify this. – David Zhang May 14 '18 at 21:20
  • Why not use regular overloading for the types you want and have them call the function template? – Nevin May 14 '18 at 21:29
  • @DavidZhang: I admit that my knowledge of explicit template instantiation is not perfect, but I was under the impression that explicitly instantiating a class template did not automatically instantiate all of its member functions. I may be wrong on this. I am fairly sure that class template argument deduction doesn't allow you to deduce a class's template arguments from arguments passed to one of its static members. – Nicol Bolas May 15 '18 at 03:49
  • @NicolBolas I know that G++ instantiates static member functions when it sees an explicit class template instantiation. (That could be a nonstandard GNU extension for all I know, but G++ is good enough for my purposes.) Good catch on the deduction though -- that indeed doesn't work. – David Zhang May 15 '18 at 04:38
  • 1
    Which approach did you use in the end? – cppBeginner Sep 22 '20 at 08:59

5 Answers5

7

You can avoid repeating function signatures by instantiating templates via taking their addresses:

// forward declarations in a header file
template<typename T>
T square(T num);

template<typename T>
T add(T left, T right);

// implementations and instantiations in a single implementation file
template<typename T>
T square(T num) {
    return num * num;
}

template<typename T>
T add(T left, T right) {
    return left + right;
}

// instantiations for specific types
#include <tuple>

template<typename... Ts>
auto instantiate() {
    static auto funcs = std::tuple_cat(std::make_tuple(
        add<Ts>,
        square<Ts>
    )...);

    return &funcs;
}

template auto instantiate<int, double>();

The overhead here is a single array of pointers to all instantiated functions, example on godbolt.

h.m.m.
  • 96
  • 1
  • (Remark: in this case the pointers are stored as a tuple, not an array.) -- By the way, it's also possible to avoid the data overhead but include the code overhead instead (https://godbolt.org/z/G7zPjP) ; however [if the value is optimized out then the functions will not be instantiated](https://godbolt.org/z/5xxfj7) (why?) – user202729 Jan 31 '21 at 00:50
  • This is a great answer, but I found with member function pointers, I had to do more work to get GCC to not emit extra object code. With C++ 17, instead of doing `template auto instantiate();`, I did this `static constexpr auto function_templates __attribute__((used)) = instantiate();` plus changing `instantiate()` to return the tuple_cat, instead of a function-local static. That forced the compiler to compute the tuple at compile time. Without that, it didn't realize that `funcs` was fully constant. – jwnimmer-tri Jun 20 '21 at 08:46
  • Here's the link to the constexpr flavor https://godbolt.org/z/vE56Wrh1f Note that there is no extra static data anymore. – jwnimmer-tri Jun 20 '21 at 08:56
  • This doesn’t prevent instantiating the speciaizations in question in every translation unit, so what does it accomplish? – Davis Herring Mar 07 '23 at 04:19
4

This is what X Macros are made for. It works quite simply.

You have a file that contains all of the types you want to apply this to. Let's call it "type_list.inc". It would look like this:

X(float)
X(double)
X(long double)

When you want to perform some operation over that list of types, you #include the file, but around the point of inclusion, you #define the macro X to do the operation you want to perform:

#define X(T) \
    template T calculate_a<T>(T x); \
    template T calculate_b<T>(T x, T y); \
    // ...
#include "type_list.inc"
#undef X

You still have to maintain two sets of function prototypes. But you only need to maintain one list of types.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
1

I haven't clearly communicated my intent in the question. The purpose of my explicit instantiations isn't to limit the types that these functions can be called with, but to inform the compiler to produce executable code for float, double, and long double

Well... if all your types are default constructible (as float, double and long double)... using folding in a template foo() function as follows

template <typename ... Ts>
void foo ()
 { ((calculate_a(Ts{}), calculate_b(Ts{}, Ts{})), ...); }

and calling foo() with desidered types

foo<float, double, long double>();

should work, I suppose.

The following is a full compiling example

template <typename T>
T calculate_a (T x)
 { return x; }

template <typename T>
T calculate_b (T x, T y)
 { return x+y; }

template <typename ... Ts>
void foo ()
 { ((calculate_a(Ts{}), calculate_b(Ts{}, Ts{})), ...); }

int main ()
 {
   foo<float, double, long double>();
 }
max66
  • 65,235
  • 10
  • 71
  • 111
0

Use regular overloading for the types you want and have them call the function template, as in:

float calculate_a(float x) { return calculate_a<float>(x); }
float calculate_b(float x, float y) { return calculate_b<float>(x, y); }

double calculate_a(double x) { return calculate_a<double>(x); }
double calculate_b(double x, double y) { return calculate_b<double>(x, y); }

long double calculate_a(long double x) { return calculate_a<long double>(x); }
long double calculate_b(long double x, long double y) { return calculate_b<long double>(x, y); }
Nevin
  • 4,595
  • 18
  • 24
  • That would work, but writing out all M\*N function declarations myself is precisely what I'm trying *not* to do. Is there a way to provide overloads that doesn't require me to write out all the signatures again? – David Zhang May 14 '18 at 21:46
  • Either you stay in template-land, where the compiler can write these for you, or you don't, in which case you have to write them yourself. About the best you can do in the latter case is use macros to ease the generation of these functions. – Nevin May 15 '18 at 16:15
0

I've found a good solution, how to do it in a "cycle", when you need to instantiate many templates with many parameter combinations: How to instantiate template methods in a "cycle"?

A.N.
  • 278
  • 2
  • 13