Как я могу кратко написать много явных экземпляров шаблонов функций? - PullRequest
0 голосов
/ 15 мая 2018

Я пишу библиотеку C ++, которая содержит множество шаблонов функций, которые я хочу явно создать и экспортировать для нескольких параметров типа.В моем конкретном случае у меня есть много шаблонов числовых функций, которые я хочу отдельно создавать и компилировать для float, double и long double.Они выглядят примерно так:

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

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

// ...

Если у меня есть M шаблонов функций и N базовых типов, то у меня есть M * N явных экземпляров для ввода.Можно ли написать эти экземпляры более кратко?

Мое текущее решение - использовать макрос препроцессора, который выполняет все экземпляры для данного типа:

#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);

Однако это неоптимально, поскольку онотребует от меня отдельно поддерживать еще одну копию подписи каждого шаблона функции.Кроме того, если я хочу сделать это в нескольких единицах перевода, то мне нужно отдельно вести список базовых типов в каждом.(Предположим, что C ++ 2a добавляет тип long long double, который я хочу поддерживать; мне придется добавить EXPLICITLY_INSTANTIATE(long long double); к каждому файлу.)

Другой возможный подход - собрать все мои функциив (статический) шаблонный класс:

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

Это решает первую проблему раздельного обслуживания двух копий каждой подписи, но требует, чтобы я изменил каждый вызов calculate_a на calculate::a<T>.Это не решает вторую проблему.

Ответы [ 4 ]

0 голосов
/ 15 мая 2018

Вы можете избежать повторения сигнатур функций, создавая экземпляры шаблонов, взяв их адреса:

// 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>();

Издержки здесь - это один массив указателей на все экземпляры функций, например, на godbolt .

0 голосов
/ 15 мая 2018

Я не ясно изложил свое намерение в этом вопросе. Цель моих явных реализаций состоит не в том, чтобы ограничить типы, с которыми эти функции могут вызываться, а в том, чтобы сообщить компилятору о создании исполняемого кода для float, double и long double

Хорошо ... если все ваши типы являются конструктивными по умолчанию (как float, double и long double) ... с использованием свертывания в шаблоне foo(), функция выглядит следующим образом

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

и вызов foo() с указанными типами

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

должно работать, я полагаю.

Ниже приведен полный пример компиляции

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>();
 }
0 голосов
/ 15 мая 2018

Это то, для чего X Макросы созданы. Работает довольно просто.

У вас есть файл, который содержит все типы, к которым вы хотите применить это. Давайте назовем это «type_list.inc». Это будет выглядеть так:

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

Когда вы хотите выполнить какую-то операцию над этим списком типов, вы #include файл, но вокруг точки включения вы #define макрос X, чтобы выполнить операцию, которую вы хотите выполнить:

#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

Вам все еще нужно поддерживать два набора прототипов функций. Но вам нужно вести только один список типов.

0 голосов
/ 15 мая 2018

Используйте обычную перегрузку для типов, которые вы хотите, и пусть они вызывают шаблон функции, как в:

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); }
...