#c #c 11 #templates #template-meta-programming #aggregate-initialization
#c #c 11 #шаблоны #шаблон-мета-программирование #агрегат-инициализация
Вопрос:
Мой вопрос, конечно, глупый, но в моем понимании C 11 и программирования шаблонов так много пробелов, что я не знаю, как подойти к этому.
Я создаю свою собственную очень простую библиотеку линейной алгебры:
typedef short index_t;
template<int M, int N, typename T = double>
class mat {
// may want some specialized constructors for mat<1,N,T> and mat<M,1,T>
public:
Tamp; operator()(index_t i, index_t j) {
return buf[i j*M];
}
Tamp; operator[](index_t k) { // useful for special cases where matrix is vector
return buf[k];
}
// etc...
private:
std::array<T, M*N> buf;
}
typedef mat<2, 1, double> col2d;
typedef mat<3, 1, double> col3d;
typedef mat<1, 2, double> row2d;
typedef mat<1, 3, double> row3d;
typedef mat<2, 2, double> mat2d;
typedef mat<3, 3, double> mat3d;
Я просто хочу, чтобы он поддерживал способ назначения (или, по крайней мере, инициализации) векторов (т. Е. Матриц с одноэлементным измерением) напрямую. Для примеров я хотел бы иметь возможность либо делать v = col2d(v1,v2)
, либо, по крайней мере col2d v = {v1,v2}
. У меня сложилось впечатление, что buf
публикация может позволить col2d v = {{v1, v2}}
, но мне не нравится идея разоблачения buf
. Я не заинтересован в написании специализированного конструктора для каждого (1, N) и каждого (M, 1). Я пытаюсь сделать библиотеку максимально простой и читаемой.
Любой совет?
Комментарии:
1. О чем
mat2d x = {{ 1,2,3,4 }};
это ? Я нахожу такую инициализацию полезной на практике. Должныcol2d
ли иrow2d
быть инициализированы таким же образом? Как насчет `mat2d x = { as_flat, {1,2,3,4} };?2. Или
mat3d x = {diagonal, 1,2,3};
? Должен ли он поступать из массива или отдельно{}
, или принятиеN*M
значений для преобразования звучит хорошо? По шкале от 1 до 10 насколько безумно вам нравится метапрограммирование? Достаточно ли хороша проверка границ времени выполнения / размера? Вы уверены , что не хотите разоблаченияbuf
? Является ли C 17 в порядке?3. Инициализация матриц — это хорошо, но я бы предпочел заниматься
M(i,j)=mij
этим, а не изучать метапрограммирование. Строки и столбцы могут быть инициализированы таким же образом, за исключением имени конструктора в случае использования конструктора, конечно. ПринятиеN*M
ценностей звучит неплохо. 3/10. О разоблаченииbuf
не может быть и речи — просто неохота. Нет C 17.
Ответ №1:
mat(std::array<T, M*N>amp;amp; buffin):buf(std::move(buffin)){}
mat(std::array<T, M*N> constamp; buffin):buf(buffin){}
Это не раскрывает buf
, но дает вам col3d x = {{{ 1,2,3 }}};
синтаксис.
Он также позволяет инициализировать mat 3d с помощью плоского буфера. Я сам нахожу это весьма полезным.
У меня может возникнуть соблазн добавить
struct flat_t{constexpr flat_t(){};};
constexpr flat_t flat{};
Затем добавьте array
ctors к mat
с помощью a flat_t
. Это предотвращает случайное неявное преобразование, сохраняя при return {flat, {{1,2,3}}};
этом возврат стиля.
Альтернативой является
template<class...Ts,
std::enavle_if_t< (sizeof...(Ts)==N*M), int> =0
>
mat(flat_t, Tsamp;amp;...ts):
buf{{std::forward<Ts>(ts)....}}
{}
Что дает вам
col2d{flat, 1,2};
как действительный col2d
.
Добавление теста sfinae, в который Ts
можно преобразовать все T
, является следующим шагом для этого пути.
Комментарии:
1. Спасибо. Не могли бы вы сказать мне, что вы думаете об ответе, который я только что опубликовал? Меня интересует любая критика, которая приходит вам на ум.
Ответ №2:
Вот что я в итоге сделал:
template<int M, int N, typename T = double>
class mat {
public:
// constructor
template <typename... Args>
mat(Args... args) : buf({ args... }) {
static_assert(sizeof...(Args) == M*N || sizeof...(Args)==0, "Wrong number of arguments in constructor.");
}
// etc...
}
// the following aliases replace all those typedefs in the question
template<index_t N, typename T=double>
using row = mat<1, N, T>;
template<index_t M, typename T=double>
using col = mat<M, 1, T>;
template <typename T=double, typename... Args>
col<sizeof...(Args), T> c(Args... args) {
return col<sizeof...(Args), T>({ args... });
}
template <typename T=double, typename... Args>
row<sizeof...(Args), T> r(Args... args) {
return row<sizeof...(Args), T>({ args... });
}
что позволяет делать такие вещи, как
c(1.0, 2.0, 3.0); // returns a col<3>, which is a mat<3,1>
r(1.0, 2.0); // returns a row<2>, which is a mat<1,2>
r<float>(1.0f, 2.0f); // returns a row<2, float>
Если есть простой способ для функции шаблона в последней строке выше вывести параметр типа из типа аргументов функции, я хотел бы узнать об этом.
РЕДАКТИРОВАТЬ: (Новая версия, включающая предложения @Yakk.)
typedef short index_t;
template<int M, int N, typename T = double>
class mat {
public:
mat() {
}
template <typename... Args, std::enable_if_t<(sizeof...(Args)==M*N), int> = 0> // static assertion doesn't work for external detection of validity, which is needed I think to avoid signature conflict with default, copy, move constructors etc.
mat(Argsamp;amp;... args) : buf({ std::forward<Args>(args)... }) {
//static_assert(sizeof...(Args) == M*N || sizeof...(Args) == 0, "Wrong number of arguments in constructor.");
}
public:
Tamp; operator()(index_t i, index_t j) {
return buf[i j*M];
}
Tamp; operator[](index_t k) {
return buf[k];
}
// etc... various matrix operations
private:
std::array<T, M*N> buf;
};
// etc... various matrix operators
// aliases for row and col
template<index_t N, typename T=double>
using row = mat<1, N, T>;
template<index_t M, typename T=double>
using col = mat<M, 1, T>;
// short-hand "literals"
template <typename... Args>
col<sizeof...(Args), typename std::common_type<Args...>::type> c(Argsamp;amp;... args) {
return col<sizeof...(Args), typename std::common_type<Args...>::type>({ std::forward<Args>(args)... });
}
template <typename... Args>
row<sizeof...(Args), typename std::common_type<Args...>::type> r(Argsamp;amp;... args) {
return row<sizeof...(Args), typename std::common_type<Args...>::type>({ std::forward<Args>(args)... });
}
Комментарии:
1. Вы должны использовать ссылки на пересылку и std forward on
Args...
. Статическое утверждение генерирует лучшее сообщение об ошибке, но предотвращает внешнее обнаружение достоверности конструктора по сравнению с тестом SFINAE. Вы можете изменятьc
иr
принимать(T arg0, Args... args)
, выводяT
из первого аргумента, если он не передан явно. Вы также можете использоватьstd::common_type
для вывода типаT
fromArgs...
, добавив atypename Explicit=void
, который переопределяет это решение, если оно передается в качестве первого аргумента шаблона вc
orr
.2. @Yakk Спасибо за очень полезный совет! Когда дело доходит до некоторой части этой магии c 11, я понимаю очень мало, но я понимаю суть того, что она выполняет, и каким-то образом мне удалось включить ваши предложения. Я не знал, как включить ваше последнее предложение, и хотел бы. Я знаю, что это не форум по обзору кода, но не могли бы вы взглянуть на мой новый код (прилагаемый к этому ответу) и рассказать мне о любых простых и простых улучшениях, которые приходят вам на ум?
3.
template<class T0, class T1, class...Args> struct get_type{using type=T0;}; template<class T1, class...Args> struct get_type<void, T1, Args...>{using type=T1;}; template<class T0, class T1, class...Ts>using get_type_t=typename get_type<T0,T1,Ts...>::type;
получитT0
, если только оно не будет недействительным, и в этом случае оно получитT1
. Расширение этого на instead getstd::common_type<T1, Ts...>::type
оставлено в качестве упражнения.4. @Yakk Для меня это слишком глубоко в тьюринговом тарпите. Я действительно доволен тем, куда ты меня уже заполучил. Еще раз спасибо.