Производный шаблон переопределить возвращаемый тип функции-члена C ++ - PullRequest
1 голос
/ 26 мая 2010

Я пишу матричные классы. Взгляните на это определение:

template <typename T, unsigned int dimension_x, unsigned int dimension_y> 
class generic_matrix
{
  ...
  generic_matrix<T, dimension_x - 1, dimension_y - 1> 
  minor(unsigned int x, unsigned int y) const
  { ... }
  ...
}

template <typename T, unsigned int dimension> 
class generic_square_matrix : public generic_matrix<T, dimension, dimension>
{
  ...
  generic_square_matrix(const generic_matrix<T, dimension, dimension>& other)
  { ... }
  ...
  void foo();
}

Класс generic_square_matrix предоставляет дополнительные функции, такие как умножение матриц. Делать это не проблема:

generic_square_matrix<T, 4> m = generic_matrix<T, 4, 4>();

Можно назначить любую квадратную матрицу для M, даже если тип не является generic_square_matrix из-за конструктора. Это возможно, потому что данные не изменяются между дочерними элементами, только поддерживаемые функции. Это также возможно:

generic_square_matrix<T, 4> m = generic_square_matrix<T, 5>().minor(1,1);

То же преобразование применимо и здесь. Но теперь возникает проблема:

generic_square_matrix<T, 4>().minor(1,1).foo(); //problem, foo is not in generic_matrix<T, 3, 3>

Чтобы решить эту проблему, я бы хотел, чтобы generic_square_matrix :: minor возвращала generic_square_matrix вместо generic_matrix. Я думаю, что единственный возможный способ сделать это - использовать специализацию шаблонов. Но поскольку специализация в основном рассматривается как отдельный класс, я должен переопределить все функции. Я не могу вызвать функцию неспецифического класса, как вы это сделали бы с производным классом, поэтому я должен скопировать всю функцию. Это не очень хорошее решение для общего программирования и много работы.

C ++ почти имеет решение для моей проблемы: виртуальная функция производного класса может возвращать указатель или ссылку на другой класс, отличный от базового класса, если этот класс является производным от класса, который возвращает базовый класс. generic_square_matrix является производным от generic_matrix, но функция не возвращает ни указатель, ни ссылку, поэтому здесь это не применимо.

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

Заранее спасибо,

Рууд

Ответы [ 4 ]

4 голосов
/ 26 мая 2010

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

Лучшим способом может быть просто использование другого имени, хотя это может нарушить "чистоту" вашего интерфейса.

Наконец, можно ли использовать minor () как свободную функцию вместо функции-члена? Тогда вы могли бы обеспечить соответствующие перегрузки, и правильная функция будет вызываться во время компиляции. Это наиболее близкое к раскрытию дела, которое я открыл, но я подозреваю, что это было бы более общепринятым как "хорошая практика".

3 голосов
/ 26 мая 2010

Нет необходимости усложнять дизайн, если вы можете просто сделать minor() бесплатной функцией:

template<typename T, unsigned int X, unsigned int Y> 
generic_matrix<T, X-1, Y-1> 
minor(const generic_matrix<T, X, Y>& m, unsigned int x, unsigned int y);

... тогда вы можете добавлять альтернативные версии столько, сколько хотите.

1 голос
/ 26 мая 2010

Если вы не используете эти типы полиморфно, вы можете просто переопределить метод базового класса в производном классе не виртуально, чтобы он возвращал объект производного класса.Это скрыло бы версию базового класса, которая обычно не нужна, но в вашем случае кажется, что это так.

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

0 голосов
/ 26 мая 2010

Шаблон специализации будет работать. Вы берете общий код и помещаете его в базовый класс, а затем просто наследуете от обоих. Защитите деструктора в базе, чтобы никто не мог сломать вашу концепцию, применив к ней базу и попытавшись удалить ее.

Примерно так:

namespace detail_
{
  template < typename T >
  struct my_templates_common_functionality
  {
    void f1() {}
    void f2() {}
  protected:
    ~my_templates_common_functionality() {}
  };
}

template < typename T >
struct my_template : detail_::my_templates_common_functionality<T>
{
  int f3() { return 5 }
};

template < >
struct my_template<double> : detail_::my_templates_common_functionality<double>
{
  char* f3() { return nullptr; }
};

Есть и другие способы, но это один из самых простых.

...