Шаблон шаблона Variadic

#c #templates #c 11

#c #шаблоны #c 11

Вопрос:

Я пытаюсь создать базовый класс, который является оболочкой вокруг std::array, который перегружает кучу распространенных арифметических операторов. Конечный результат будет чем-то вроде std::valarray, но со статическим размером. Я делаю это, потому что создаю целый ряд дочерних классов для своей библиотеки, которые в конечном итоге воспроизводят эту функциональность. Например, мне нужно создать класс MyPixel и класс MyPoint, оба из которых по сути являются просто массивами статического размера, с которыми я могу выполнять арифметику.

Мое решение заключается в создании базового класса StaticValArray, из которого могут быть производными MyPoint и MyPixel. Однако, чтобы запретить пользователям добавлять MyPoint в MyPixel, я использую шаблон CRTP как таковой:

 template<class T1, class T2>
struct promote
{
  typedef T1 type; // Assume there is a useful type promotion mechanism here
};

template<class T, size_t S, template<typename... A> class ChildClass>
class StaticValArray : public std::array<T,S>
{
  public:
    // Assume there are some conversion, etc. constructors here...

    template<class U>
    StaticValArray<typename promote<T,U>::type,S,ChildClass> operator  
        (StaticValArray<U,S,ChildClass> const amp; rhs)
    {
      StaticValArray<typename promote<T,U>::type,S,ChildClass> ret = *this;
      std::transform(this->begin(), this->end(),
          rhs.begin(), ret.begin(), std::plus<typename promote<T,U>::type>());
      return ret;
    }


    // More operators....
};
  

Это довольно круто, потому что дочерний класс может иметь любые произвольные параметры шаблона класса, и эта штука будет работать. Например:

 template<class T, class U>
class MyClassTwoTypes : public StaticValArray<T,3,MyClassTwoTypes>
{ };

template<class T, class U>
class MyClassTwoTypes2 : public StaticValArray<T,3,MyClassTwoTypes2>
{ };

int main()
{
  MyClassTwoTypes<int, float> p;
  MyClassTwoTypes<double, char> q;
  auto z = p   q;

  MyClassTwoTypes2<double, char> r;
  //  r  = q;  // <-- Great! This correctly won't compile

  return 0;
}
  

Моя проблема заключается в следующем: я хотел бы вставить некоторый дочерний класс в бит CRTP StaticValArray, который не обязательно содержит только классы в качестве параметров шаблона. Для примера рассмотрим этот N-мерный класс точек:

 template<class T, size_t S>
class MyPointND : public StaticValArray<T,S,MyPointND>
{ };
  

К сожалению, это не будет компилироваться, потому что size_t не является именем типа — я получаю ошибку компилятора:

 type/value mismatch at argument 3 in template parameter list fortemplate<class T, long unsigned int S, template<class ... A> class ChildClass> class StaticValArray’
test.C:36:54: error:   expected a template of type ‘template<class ... A> class ChildClass’, got ‘template<class T, long unsigned int S> class MyPointND 

Есть ли какой-либо способ создать пакет параметров шаблона variadic template, который может быть абсолютно любым (имена типов, целые числа, size_t, doubles, что угодно?) потому что, в конце концов, мне действительно все равно, какой там тип. Обратите внимание, что я не могу просто полностью указать дочерний класс (например class MyPointND: public StaticValArray<T,S,MyPointND<T,S>> ), потому что это нарушило бы мой механизм продвижения типа.

Комментарии:

1. Что касается promote структуры, вы можете воспользоваться преимуществами decltype : typedef decltype(T U) type; внутри структуры.

2. Моя реальная реализация продвижения использует это, я просто называю это «продвигать», чтобы было понятно. Подробности здесь опущены просто для краткости.

Ответ №1:

Что, если бы вместо size_t вы использовали std::integral_constant? Вы бы вставили в него числовое значение размера вашего массива и могли бы использовать его в качестве типа.

Редактировать

Чтобы уменьшить детализацию, вы могли бы определить свой собственный класс integral constant, что-то вроде:

 template <std::size_t N>
struct size_ : std::integral_constant<std::size_t,N> {};
  

Тогда вы могли бы использовать его следующим образом:

 MyPointND<int,size_<3>> x;
  

Комментарии:

1. Хм, кажется, это в правильном направлении! К сожалению, создание экземпляра MyPointND становится немного болтливым: MyPointND<int, std::integral_constant<int,3>> x;

2. Что вы могли бы сделать, так это создать свой собственный класс, производный от std::integral (отредактированный пост выше).

Ответ №2:

Что вам нужно сделать, так это иметь traits класс, специализированный для каждого типа, содержащий все, что вам нужно для расширения типа, а затем передать полный тип в StaticValArray.

Более того, с decltype вам не должно понадобиться ничего подобного — decltype скажет вам, что вы получите, добавив float и int.

 template<class U>
StaticValArray<decltype(*(T*)nullptr   *(U*)nullptr),S,ChildClass> operator  
    (StaticValArray<U,S,ChildClass> const amp; rhs)
{
  StaticValArray<decltype(*(T*)nullptr   *(U*)nullptr),S,ChildClass> ret = *this;
  std::transform(this->begin(), this->end(),
      rhs.begin(), ret.begin(), std::plus<decltype(*(T*)nullptr   *(U*)nullptr)>());
  return ret;
}
  

Комментарии:

1. Извините, я, должно быть, что-то упускаю — не могли бы вы немного рассказать о классе traits?

2. DeadMG: не было бы подходящим использовать std::declval вместо приведения и разыменования nullptr?