Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I concisely write a lot of explicit function template instantiations?

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.

like image 706
David Zhang Avatar asked May 14 '18 21:05

David Zhang


People also ask

How do you write a function template?

A function template starts with the keyword template followed by template parameter(s) inside <> which is followed by the function definition. In the above code, T is a template argument that accepts different data types ( int , float , etc.), and typename is a keyword.

What is the importance of function templates?

Function templates are special functions that can operate with generic types. This allows us to create a function template whose functionality can be adapted to more than one type or class without repeating the entire code for each type.

What do you understand by template explain function template with suitable example?

A template is a simple yet very powerful tool in C++. The simple idea is to pass data type as a parameter so that we don't need to write the same code for different data types. For example, a software company may need to sort() for different data types.

Which is best suited syntax for template function?

Which one is suitable syntax for function template? Explanation: Both class and typename keywords can be used alternatively for specifying a generic type in a template.


2 Answers

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.

like image 143
h.m.m. Avatar answered Sep 25 '22 01:09

h.m.m.


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.

like image 42
Nicol Bolas Avatar answered Sep 23 '22 01:09

Nicol Bolas