Как сделать так, чтобы мой векторный класс поддерживал прямую инициализацию / назначение

#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 from Args... , добавив a typename Explicit=void , который переопределяет это решение, если оно передается в качестве первого аргумента шаблона в c or r .

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 get std::common_type<T1, Ts...>::type оставлено в качестве упражнения.

4. @Yakk Для меня это слишком глубоко в тьюринговом тарпите. Я действительно доволен тем, куда ты меня уже заполучил. Еще раз спасибо.