2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу 1, 2  След.
 
 C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 16:25 


11/08/18
363
Добрый день,

переписываю довольно большую библиотеку, в которой запланирована масса функций вида
Код:
int cur_func(int N, double *A)
{ // какой-то код
  dfunc(N, A);
}


int cur_func(int N, __complex__ double *A)
{ // какой-то код, полностью идентичный коду в блоке "какой-то код" в функции выше
  ini();
  zfunc(N, (doublecomplex*)A);
}

то есть одна и та же функция cur_func, в зависимости от того, какой тип ей подали, должна перевызывать другие функции dfunc или zfunc при необходимости, кастить аргументы а в некоторых случаях дополнительно что-то еще вызывать.

Хочу повысить читаемость кода и уберечь себя от описок при написании этой библиотеки, поэтому хочу, чтобы было все примерно так
Код:
template<typename DT> int cur_func(int N, DT A)
{ // какой-то код одинаковый для любого из вариантов
#if DT==__complex__ double
  ini();
#endif
  NAMECONV(DT,func)(N, TYPECONV(DT)A);
}

а NAMECONV(DT,FUNC) как-то бы раскрывалось в
Код:
#if(DT==double)
d##FUNC
#else
z##FUNC
#endif

и TYPECONV(DT) как-то бы раскрывалось в
Код:
#if(DT!=double)
(doublecomplex*)
#endif

но, очевидно, что в таком синтексисе это не компилиться.

Пожалуйста, посоветуйте, есть ли какие-то правильные конструкции в С++ (17 или любого другого стандарта), которые бы позволили бы мне такое сделать?

Спасибо!

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 16:30 
Заслуженный участник
Аватара пользователя


16/07/14
8339
Цюрих
constexpr if
Используется синтаксис C++
if consexpr (std::is_same_v<DT, double>) { a(); } else { b(); }

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 16:53 


11/08/18
363
Спасибо большое, mihaild, вот здорово, именно то, что надо!!!

-- 20.02.2022, 16:42 --

Все-таки я повидимому, что-то не понимаю, пожалуйста, скажите, можно ли как-то сделать следующее:

Код:
#define LA(D,A) (if constexpr (std::is_same_v<D, double>) d##A; else z##A;)

inline doublecomplex *LC(__complex__ double *A) { return (doublecomplex*)A; }
inline double        *LC(double *A) { return A; }

template<typename DT> inline void COPY(int N, DT *X, int IX, DT *Y, int IY) { LA(DT,copy(N, LC(X), IX, LC(Y), IY)); }


здесь я сделал LC как функию преобразования типа, чтобы компилятор не ругался, а вот LA - по задумке должно было подсатавить буковку "d" или "z" перед названием, и, у меня так ничего и не получилось. Я понимаю, что я могу сделать так:

Код:
inline doublecomplex *LC(__complex__ double *A) { return (doublecomplex*)A; }
inline double        *LC(double *A) { return A; }

template<typename DT> inline void COPY(int N, DT *X, int IX, DT *Y, int IY) {
if(constexpr(std::is_same_v<D, double>))
  dcopy(N, X, IX, Y, IY);
else
  zcopy(N, LC(X), IX, LC(Y), IY);
}

но хочется ведь красивее. Вдруг есть проще решение, пожалуйста, посоветуйте!

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 18:18 
Заслуженный участник
Аватара пользователя


16/07/14
8339
Цюрих
В принципе можно - покажите весь релевантный код и сообщение об ошибке. Но не нужно - препроцессор использовать стоит только в самых крайних случаях, продублировать вызов функции - совсем не страшно.

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 18:30 
Заслуженный участник


09/05/12
25179
 !  ilghiz, а еще будет существенно лучше, если вы при написании сообщений будете пользоваться не тэгом кода, а тэгом подсветки синтаксиса.

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 18:33 


09/05/16
138
Я понимаю, что это другой язык (C11, в C++ этого нет), но если вынести часть кода в секцию на C, можно воспользоваться generic selection для выбора функции в зависимости от типа аргумента. Шаблоны с препроцессором, действительно, не всегда хорошо сочетаются.

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 19:09 


11/08/18
363
Огромное спасибо за ответы!

Я сильно запутался и хочу выбрать правильную концепцию.

У меня есть около 200 тысяч строк математического кода, который интенсивно пользует lapack/blas и их различные версии.

В моей старой концепции все функции называются COPY вместо dcopy, zcopy, scopy, ccopy, и еще нескольких "своих" версий qdcopy, qzcopy (версия с четверной точностью), hdcopy, рzcopy (версия с автоматической подстановкой вместо double специальной структуры для дальнейшего использования этого всего в методах Баура-Штрассена).

Все это у меня было написано на С99, и недавно я решил таки перейти на С++, так как Бауер-Штрассен, да и все остальное, гораздо прозрачнее пишутся при использовании перегрузки операторов.

Что мне надо. У меня есть синтаксис blas/lapack, в котором имеется что-то типа

void dcopy(int N, double *X, int IX, double *Y, int IY);

но таких функций у меня много, и они называются по-разному, dcopy, zcopy, scopy, ccopy, hdcopy, hzcopy, hdcopy, hzcopy, qdcopy, qzcopy, (может и больше будет) и для каждой имеется свой вариант типов аргументов (double, float, DoubleDouble, DoubleFloat, GradDouble, GradFloat, ...)

Все также ухудшается тем, что стандартный Lapack/Blas бывает от разных производителей (intel math kernel library, netlib, embedded clapack) и эти все козлы (по другому не скажешь) слегка меняют аргументы (то int по ссылке, то как аргумент, то doublecomplex, то __complex__ double) и я хочу, чтобы все это всегда и везде работало.

Раньше так и было, но все было сделано на огромном числе #define на обычном С99, а сейчас мы приняли решение перейти на С++ и я вешаюсь, как мне это разумно сделать...

Хочу так:

Используется синтаксис C++
#define LA(D,A) (if constexpr (std::is_same_v<D, double>) d##A; else z##A;)

inline doublecomplex *LC(__complex__ double *A) { return (doublecomplex*)A; }
inline double        *LC(double *A) { return A; }


// BLAS-1

template<typename DT> inline void COPY(int N, DT *X, int IX, DT *Y, int IY) { LA(DT,copy(N, LC(X), IX, LC(Y), IY)); }
template<typename DT> inline double NRM2(int N, DT *X, int IX) { return LA(DT,nrm2(N, LC(X), IX)); }
template<typename DT> inline double NRM2_2(int N, DT *X, int IX) { DT a=LA(DT,nrm2(N, LC(X), IX)); return a*a; }
template<typename DT> inline double ASUM(int N, DT *X, int IX) { return LA(DT,asum(N, LC(X), IX)); }
template<typename DT> inline int IAMAX(int N, DT *X, int IX) { return LAI(DT,amax(N, LC(X), IX)); }
 


но компилятор ругается

Цитата:
g_lapack.h: In function ‘void COPY(int, DT*, int, DT*, int)’:
g_lapack.h:47:18: error: expected primary-expression before ‘if’
47 | #define LA(D,A) (if constexpr (std::is_same_v<D, double>) d##A; else z##A;)
| ^~
g_lapack.h:56:79: note: in expansion of macro ‘LA’
56 | template<typename DT> inline void COPY(int N, DT *X, int IX, DT *Y, int IY) { LA(DT,copy(N, LC(X), IX, LC(Y), IY)); }
| ^~
g_lapack.h:47:18: error: expected ‘)’ before ‘if’
47 | #define LA(D,A) (if constexpr (std::is_same_v<D, double>) d##A; else z##A;)
| ~^~
g_lapack.h:56:79: note: in expansion of macro ‘LA’
56 | template<typename DT> inline void COPY(int N, DT *X, int IX, DT *Y, int IY) { LA(DT,copy(N, LC(X), IX, LC(Y), IY)); }
| ^~
g_lapack.h:47:65: error: ‘else’ without a previous ‘if’
47 | #define LA(D,A) (if constexpr (std::is_same_v<D, double>) d##A; else z##A;)
| ^~~~
g_lapack.h:56:79: note: in expansion of macro ‘LA’
56 | template<typename DT> inline void COPY(int N, DT *X, int IX, DT *Y, int IY) { LA(DT,copy(N, LC(X), IX, LC(Y), IY)); }
| ^~
g_lapack.h:47:75: error: expected primary-expression before ‘)’ token
47 | #define LA(D,A) (if constexpr (std::is_same_v<D, double>) d##A; else z##A;)
| ^
g_lapack.h:56:79: note: in expansion of macro ‘LA’
56 | template<typename DT> inline void COPY(int N, DT *X, int IX, DT *Y, int IY) { LA(DT,copy(N, LC(X), IX, LC(Y), IY)); }
| ^~
g_lapack.h: In function ‘double NRM2(int, DT*, int)’:
g_lapack.h:47:18: error: expected primary-expression before ‘if’
47 | #define LA(D,A) (if constexpr (std::is_same_v<D, double>) d##A; else z##A;)
| ^~


Почему я хочу define? Так как я хочу измемнить однажды этот define и подставить туда ту функцию (dcopy, zcopy, qdcopy, qzcopy, gdcopy, gzcopy, etc.) что мне надо в зависимости от типа данных.

Если внутри каждой моей функции я буду писать if constexpr ... и подставлять то, что надо, то будет громоздко.

Пока я начал писать так:

  1. inline void COPY(int N, double *X, int IX, double *Y, int IY) { dcopy(N, X, IX, Y, IY); } 
  2. inline void COPY(int N, __complex__ double *X, int IX, double *Y, int IY) { zcopy(N, (doublecomplex*)X, IX, (doublecomplex*)Y, IY); } 


но в моем случае (8 разных типов + 4 разных поставщиков библиотек) приводит просто к уйме однотипного кода, и, вероятность опечатки тут просто растет экспоненциально.

Вдруг Вы бы смогли бы посоветовать, как, в зависимости от типа данных в template я бы мог автоматически подставлять правильный префикс в название - это бы мне очень сильно помогло!

Спасибо!

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 20:02 
Заслуженный участник
Аватара пользователя


16/07/14
8339
Цюрих
ilghiz в сообщении #1549177 писал(а):
но компилятор ругается
Ну так посмотрите, какой у вас код после препроцессора получается (-E в clang или gcc), там какие-то странные скобки.
Но в целом повторю, макросы в С++ использовать обычно не надо.
Правильный путь здесь - не заводить n функций с разными именами, а перегрузить одну.

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 21:44 


11/08/18
363
Спасибо большое за ответ!

mihaild в сообщении #1549190 писал(а):
Правильный путь здесь - не заводить n функций с разными именами, а перегрузить одну.

Так не я их писал, а я пользую эти функции от нескольких стандартных библиотек + две свои библиотеки, исторически написанные 10-20 лет назад.

Желания их переписывать - совершенно нет, там больше 10 миллионов строк, и все это заново отлаживать - просто застрелиться (причем большая часть - это не наше).

mihaild в сообщении #1549190 писал(а):
Но в целом повторю, макросы в С++ использовать обычно не надо.

Это и понятно, поэтому хочу сделать не на макросах, так как решили перейти с С на С++.

mihaild в сообщении #1549190 писал(а):
ilghiz в сообщении #1549177 писал(а):
но компилятор ругается
Ну так посмотрите, какой у вас код после препроцессора получается (-E в clang или gcc), там какие-то странные скобки.


Я понимаю, что что-то не так, но -E не помогает мне понять, что тут сделать. Выхлоп после -E таков:

Используется синтаксис C++
inline doublecomplex *LC(__complex__ double *A) { return (doublecomplex*)A; }
inline double *LC(double *A) { return A; }

template<typename DT> inline void COPY(int N, DT *X, int IX, DT *Y, int IY) { (if constexpr (std::is_same_v<DT, double>) dcopy(N, LC(X), IX, LC(Y), IY); else zcopy(N, LC(X), IX, LC(Y), IY);); }
template<typename DT> inline double NRM2(int N, DT *X, int IX) { return (if constexpr (std::is_same_v<DT, double>) dnrm2(N, LC(X), IX); else znrm2(N, LC(X), IX);); }
template<typename DT> inline double NRM2_2(int N, DT *X, int IX) { DT a=(if constexpr (std::is_same_v<DT, double>) dnrm2(N, LC(X), IX); else znrm2(N, LC(X), IX);); return a*a; }
template<typename DT> inline double ASUM(int N, DT *X, int IX) { return (if constexpr (std::is_same_v<DT, double>) dasum(N, LC(X), IX); else zasum(N, LC(X), IX);); }
template<typename DT> inline int IAMAX(int N, DT *X, int IX) { return (if constexpr (std::is_same_v<DT, double>) idamax(N, LC(X), IX); else izamax(N, LC(X), IX);); }
 


и я понимаю, что я что-то делаю не так, но не могу сообразить что именно.

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 22:03 
Заслуженный участник
Аватара пользователя


16/07/14
8339
Цюрих
ilghiz в сообщении #1549212 писал(а):
Желания их переписывать - совершенно нет
Не надо переписывать эти функции, напишите вызывающие их перегрузки.
ilghiz в сообщении #1549212 писал(а):
{ (if constexpr
А что тут могла бы значить скобка перед if?

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 22:12 


11/08/18
363
mihaild в сообщении #1549219 писал(а):
А что тут могла бы значить скобка перед if?

да, похоже накосячил, спасибо!!! Хотел в случае возвращаемого значения через скобку его вернуть. Пока переписал так:

код: [ скачать ] [ спрятать ]
Используется синтаксис C++
#define LA(D,A) if constexpr (std::is_same_v<D, double>) d##A; else z##A;
#define LAX(X,D,A) if constexpr (std::is_same_v<D, double>) X=d##A; else X=z##A;
#define LAI(D,A) if constexpr (std::is_same_v<D, double>) id##A; else iz##A;
#define LAXI(X,D,A) if constexpr (std::is_same_v<D, double>) X=id##A; else X=iz##A;

inline doublecomplex *LC(__complex__ double *A) { return (doublecomplex*)A; }
inline double        *LC(double *A) { return A; }

// BLAS-1

template<typename DT> inline void COPY(int N, DT *X, int IX, DT *Y, int IY) { LA(DT,copy(N, LC(X), IX, LC(Y), IY)); }
template<typename DT> inline double NRM2(int N, DT *X, int IX) { double ret; LAX(ret,DT,nrm2(N, LC(X), IX)); return ret; }
template<typename DT> inline double NRM2_2(int N, DT *X, int IX) { double ret; LAX(ret,DT,nrm2(N, LC(X), IX)); return ret*ret; }
template<typename DT> inline double ASUM(int N, DT *X, int IX) { double ret; LAX(ret,DT,asum(N, LC(X), IX)); return ret; }
template<typename DT> inline int IAMAX(int N, DT *X, int IX) { int ret; LAXI(ret,DT,amax(N, LC(X), IX)); return ret; }
 


хотя криво как-то, хотя и не график :)

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 22:39 


18/09/21
1676
ilghiz в сообщении #1549150 писал(а):
Хочу повысить читаемость кода и уберечь себя от описок при написании этой библиотеки, поэтому хочу, чтобы было все примерно так
Это очень вредная идея.
Изначально, где использовалась перегрузка функции - это наиболее оптимальный вариант (для лучшей читаемости и меньшего риска потенциальных ошибок).

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 23:01 


11/08/18
363
zykov в сообщении #1549228 писал(а):
ilghiz в сообщении #1549150 писал(а):
Хочу повысить читаемость кода и уберечь себя от описок при написании этой библиотеки, поэтому хочу, чтобы было все примерно так
Это очень вредная идея.
Изначально, где использовалась перегрузка функции - это наиболее оптимальный вариант (для лучшей читаемости и меньшего риска потенциальных ошибок).


Так я как раз пытаюсь уменьшить число описок.

Вот представьте, вы написали сопряженный градиент, пусть даже без предобуславливателя и хотите его использовать всегда и везде.

У него есть несколько версий:

    1. действительная версия на двойной точности (для более-менее обычных задач),
    2. комплексная версия на двойной точности (для более-менее обычных задач),
    3. извращенная версия, когда двумя float вы получаете почти один double для ущербных графических карт и для ущербных embedded процессоров, у которых двойная точность или через эмулятор, или ну сказочно медленно,
    4. для извращенных задач от нескольких миллиардов неизвестных, когда скалярное произведение уже хорошо не стабильно на двойной точности, а аппаратной четверной точности нет,
    5. для извращений, когда хочется считать аналитический градиент методом Бауера-Штрассена, но в минимизируемой функции присутствует ункция вычисления сопряженного градиента.
Грубо говоря, кроме сопряженного градиента у меня там же есть туча других мыслимых и не мыслимых итерационных солверов, всяких там тензорных разложений и еще всякого добра, которое десятилетиями отлаживалось и на данный момент работает.

Но все это есть на С99, и пришло время спортировать это все на С++.

Теперь...

Можно оставить как есть, и на каждую функцию и каждый ее вариант написать унитест, потрахаться с ними еще пару десятков человеколет, когда вместо современного С++17 версии уже будет что-то другое.

А можно... найти способ автоматической конвертации через темплейты и какие-то правильные структуры, как мы тут выше обсуждали, и, прогнав готовые тестовые примеры быть практически уверенным, что все это (лапаки на десяток миллионов строк + свое все библиотеки, пользующие эти лапаки на сотни тысяч строк) после такой конвертации будут надежно работать.

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение20.02.2022, 23:35 


18/09/21
1676
Тут зависит от того, что внутри функции.
Если там разный код, то лучше так и оставить разные перегруженные функции.
Если код почти одинаковый, как например в алгоритме сортировки, то можно сделать шаблон, который будет использоваться для разных типов данных (для каждого типа будет вызываться свой оператор сравнения).
Снижение дублирования в коде, это конечно хорошо, но тут смотря по тому, какой ценой это достигается. Может получиться плохо читаемый код уязвимый для ошибок.

Злоупотреблять препроцессором в C++ - это однозначно плохой стиль.
Препроцессор можно использовать только в исключительных случаях и стараться, чтобы это было локализованно в малом куске кода (какой-нибудь специальный заголовок или ещё лучше - маленький исходный файл).

 Профиль  
                  
 
 Re: C++ как использовать тип в template для условной компиляции?
Сообщение21.02.2022, 00:26 


11/08/18
363
Спасибо большое, за ответы!

zykov в сообщении #1549236 писал(а):
Тут зависит от того, что внутри функции.
Если там разный код, то лучше так и оставить разные перегруженные функции.


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

Возьмем набор функций умножения матриц:
GEMM(TA, TB, N, M, K, Alpha, A, LDA, B, LDA, Beta, C, LDC);
TA, TB - переключатели транспонированная, комплексно сопряженная или еще какая это матрица,
A, LDA, B, LDB, C, LDC - матрицы с их ведущими размерностями,
Alpha, Beta - скаляры

эта функция у меня используется в нашей библиотеке высокого уровня именно в таком синтаксисе тысячи раз, но, в зависимости от того, где она используется, там должна подставиться одна из 8 функций (в будущем будет больше), которые имплементированы или нашими низкоуровневыми библиотеками, или сторонними библиотеками, и выбор какую подставить - определяет то, для чего я сейчас компилирую эту библиотеку высокого уровня.

zykov в сообщении #1549236 писал(а):
Тут зависит от того, что внутри функции.
Препроцессор можно использовать только в исключительных случаях и стараться, чтобы это было локализовано в малом куске кода (какой-нибудь специальный заголовок или ещё лучше - маленький исходный файл).

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

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 17 ]  На страницу 1, 2  След.

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group