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
9166
Цюрих
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
9166
Цюрих
В принципе можно - покажите весь релевантный код и сообщение об ошибке. Но не нужно - препроцессор использовать стоит только в самых крайних случаях, продублировать вызов функции - совсем не страшно.

 Профиль  
                  
 
 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
9166
Цюрих
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
9166
Цюрих
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
1756
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
1756
Тут зависит от того, что внутри функции.
Если там разный код, то лучше так и оставить разные перегруженные функции.
Если код почти одинаковый, как например в алгоритме сортировки, то можно сделать шаблон, который будет использоваться для разных типов данных (для каждого типа будет вызываться свой оператор сравнения).
Снижение дублирования в коде, это конечно хорошо, но тут смотря по тому, какой ценой это достигается. Может получиться плохо читаемый код уязвимый для ошибок.

Злоупотреблять препроцессором в 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, Супермодераторы



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

Сейчас этот форум просматривают: worm2


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

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