C ++ неявная реализация шаблона - PullRequest
7 голосов
/ 16 ноября 2009

У меня в настоящее время есть иерархия классов, подобная

MatrixBase -> DenseMatrix
           -> (other types of matrices)
           -> MatrixView -> TransposeView
                         -> DiagonalView
                         -> (other specialized views of matrices)

MatrixBase - абстрактный класс, который вынуждает разработчиков определять operator () (int, int) и тому подобное; он представляет 2-х мерные массивы чисел. MatrixView представляет (возможно изменяемый) способ просмотра матрицы, например, ее транспонирование или подматрицу. Смысл MatrixView в том, чтобы иметь возможность сказать что-то вроде

Scale(Diagonal(A), 2.0)

где Diagonal возвращает объект DiagonalView, который является своего рода облегченным адаптером.

Теперь вот вопрос (ы). Я буду использовать очень простую матричную операцию в качестве примера. Я хочу определить функцию как

template <class T>
void Scale(MatrixBase<T> &A, const T &scale_factor);

, который делает очевидную вещь, которую предлагает название. Я хочу быть в состоянии передать либо матрицу отсутствия представления о честности, либо экземпляр подкласса MatrixView. Прототип, как написано выше, не работает для таких утверждений, как

Scale(Diagonal(A), 2.0);

, поскольку объект DiagonalView, возвращаемый Diagonal, является временным, а Scale принимает неконстантную ссылку, которая не может принять временную. Есть ли способ сделать эту работу? Я пытался использовать SFINAE, но я не очень хорошо понимаю это, и я не уверен, что это решит проблему. Для меня важно, чтобы эти шаблонные функции можно было вызывать без предоставления явного списка аргументов шаблона (я хочу неявную реализацию). В идеале вышеприведенное утверждение может работать как написано.


Редактировать: (следующий вопрос)

Как sbi ответил ниже о ссылочных и временных значениях rvalue, есть ли способ определить две версии Scale, одну, которая принимает неконстантную ссылку на rvalue для не-представлений, и другую, которая принимает представление по значению? Проблема состоит в том, чтобы различать эти два во время компиляции таким образом, чтобы неявная реализация работала.


Обновление

Я изменил иерархию классов на

ReadableMatrix
WritableMatrix : public ReadableMatrix
WritableMatrixView
DenseMatrix : public WritableMatrix
DiagonalView : public WritableMatrixView

Причина, по которой WritableMatrixView отличается от WritableMatrix, заключается в том, что представление должно передаваться по константной ссылке, в то время как сами матрицы должны передаваться по неконстантной ссылке, поэтому функции-члены доступа имеют различное постоянство , Теперь такие функции, как Scale, могут быть определены как

template <class T>
void Scale(const WritableMatrixView<T> &A, const T &scale_factor);
template <class T>
void Scale(WritableMatrix<T> &A, const T &scale_factor){
    Scale(WritableMatrixViewAdapter<T>(A), scale_factor);
}

Обратите внимание, что существует две версии: одна для константного представления и неконстантная версия для реальных матриц. Это означает, что для таких функций, как Mult(A, B, C), мне потребуется 8 перегрузок, но по крайней мере это работает. Однако, что не работает, так это использование этих функций в других функциях. Видите ли, каждый View -подобный класс содержит член View того, на что он смотрит; например, в выражении Diagonal(SubMatrix(A)) функция Diagonal возвращает объект типа DiagonalView<SubMatrixView<T> >, который должен знать полностью производный тип A. Теперь предположим, что в Scale я вызываю какую-то другую функцию, подобную этой, которая принимает либо базовое представление, либо ссылку на матрицу. Это потерпит неудачу, потому что для построения необходимых View требуется производный тип аргумента Scale; информации у него нет. Все еще работаем над поиском решения этой проблемы.


Обновление

Я использовал собственную версию Boost's enable_if для выбора между двумя различными версиями функции, например Scale. Это сводится к маркировке всех моих классов матриц и представлений с помощью дополнительных тегов typedef, указывающих, доступны ли они для чтения и записи, а также для просмотра или отсутствия просмотра. В конце мне все еще нужно 2 ^ N перегрузок, но теперь N - это только число неконстантных аргументов. Для окончательного результата см. здесь (вряд ли он будет серьезно обновлен снова).

Ответы [ 5 ]

7 голосов
/ 16 ноября 2009

Это не имеет ничего общего с шаблонами. Ваш пример

Scale(Diagonal(A), 2.0);

может быть обобщено до

f(g(v),c);

В C ++ 03 для этого требуется, чтобы первый параметр f() был передан для каждой копии или для ссылки const. Причина в том, что g() возвращает временное значение. Однако значения r привязывают только к const ссылкам, но не к неконстантным ссылкам. Это не зависит от того, участвуют ли шаблоны, SFINAE, TMP или еще много чего. Это просто язык (в настоящее время).

За этим также стоит логическое обоснование: если g() возвращает временное значение, а f() изменяет это временное значение, то никто не может "увидеть" измененное временное состояние. Таким образом, изменение сделано напрасно, и все это, скорее всего, ошибка.

Насколько я понял, в вашем случае результат g() является временным, это представление какого-то другого объекта (v), поэтому изменение его приведет к изменению v. Но если это так, в текущем C ++ результат g() должен быть либо const (чтобы его можно было связать со ссылкой const, либо его нужно скопировать. Так как const "пахнет" неправильно мне, сделав такой взгляд дешевым для копирования, вероятно, было бы лучше всего.

Однако это еще не все. C ++ 1x представит так называемые rvalue ссылки. То, что мы знаем как «ссылки», будет затем разделено на ссылки на lvalue или ссылки на rvalue. Вы сможете иметь функции, принимающие rvalue-ссылки и даже перегруженные на основе «l / rvalue-ness». Это было задумано, чтобы позволить дизайнерам классов перегрузить копию ctor и присваивание для r-значения правой части и заставить их «красть» значения правой части, так что копирование r-значений будет дешевле. Но вы, вероятно, могли бы использовать его, чтобы Scale взял rvalue и изменил его.

К сожалению, ваш компилятор, скорее всего, пока не поддерживает ссылки на rvalue.


Редактировать (следующий вопрос) :

Вы не можете перегрузить f(T) с помощью f(T&), чтобы достичь того, что вы хотите. Хотя для значений rvalue будут использоваться только первые, lvalues ​​может одинаково хорошо связываться с любым аргументом, поэтому вызов f с lvalue неоднозначен и приводит к ошибке во время компиляции.

Однако, что не так с перегрузкой для DiagonalView:

template <class T>
void Scale(MatrixBase<T> &matrix, const T &scale_factor);

template <class T>
void Scale(DiagonalView<T> view, const T &scale_factor);

Есть что-то, что я пропускаю?


Другое редактирование :

Тогда мне понадобится смехотворно большое количество перегрузок, поскольку в настоящее время существует более 5 представлений и несколько десятков функций, таких как Scale.

Тогда вам нужно сгруппировать те типы, которые могут обрабатываться одинаково. Вы можете использовать некоторые простые шаблоны-мета для группировки. С макушки головы:

template<bool B>
struct boolean { enum { result = B }; };

template< typename T >
class some_matrix {
  public:
    typedef boolean<false> is_view;
  // ...
};

template< typename T >
class some_view {
  public:
    typedef boolean<true> is_view;
  // ...
};

namespace detail {
  template< template<typename> class Matrix, typename T >
  void Scale(Matrix<T>& matrix, const T& scale_factor, boolean<true>)
  {
    /* scaling a matrix*/
  }
  template< template<typename> class Matrix, typename T >
  void Scale(View<T>& matrix, const T& scale_factor, boolean<true>)
  {
    /* scaling a view */
  }
}

template< template<typename> class Matrix, typename T >
inline void Scale(Matrix<T>& matrix, const T& scale_factor)
{
  detail::Scale( matrix, scale_factor, typename Matrix<T>::is_view() );
}

Эта конкретная настройка / группировка может не совсем соответствовать вашим потребностям, но вы можете настроить что-то подобное так, как вам удобно.

1 голос
/ 16 ноября 2009

Простой способ исправить это - использовать boost::shared_ptr< MatrixBase<T> > вместо ссылки.

0 голосов
/ 16 ноября 2009

Не используйте ссылки, передавайте по значению.

Пусть copy elision выполнит для вас оптимизацию, если это необходимо.

0 голосов
/ 16 ноября 2009

вы ограничиваете тип первого аргумента Scale, но вы можете позволить компилятору самому определить, какой тип будет подходящим, например:

template <class M,class T>
void Scale(M A, const T &scale_factor);
0 голосов
/ 16 ноября 2009

Может быть, вы должны использовать const.

template <class T>
void Scale(const MatrixBase<T> &A, const T &scale_factor);
...