#c #templates #c 11 #enable-if
#c #шаблоны #c 11 #включить-если
Вопрос:
У меня есть (в основном завершенный) класс matrix (позже в этом посте). Если матрица представляет собой матрицу 1×1, то я хотел бы иметь неявное преобразование в тип поддержки (например, матрица с плавающей запятой 1×1 должна преобразовываться в значение с плавающей запятой).
Есть ли способ сделать это без создания специализации и дублирования всех методов внутри Matrix? (например, используя что-то вроде std::enable_if
?) Я в основном хочу включить неявное преобразование тогда и только тогда, когда СТРОКИ == COLS == 1.
template <std::size_t ROWS, std::size_t COLS = 1, typename BackingType = float>
class Matrix
{
BackingType data[ROWS][COLS];
public:
Matrix()
{
for(std::size_t rdx = 0; rdx < ROWS; rdx)
{
for (std::size_t cdx = 0; cdx < COLS; cdx)
{
data[rdx][cdx] = 0;
}
}
}
const BackingTypeamp; Member(std::size_t index) const
{
assert(index < ROWS*COLS);
return *(static_cast<BackingType*>(amp;data[0][0]) index);
}
BackingTypeamp; Member(std::size_t index)
{
assert(index < ROWS*COLS);
return *(static_cast<BackingType*>(amp;data[0][0]) index);
}
const BackingTypeamp; Member(std::size_t rowIndex, std::size_t colIndex) const
{
assert(rowIndex < ROWS);
assert(colIndex < COLS);
return data[rowIndex][colIndex];
}
BackingTypeamp; Member(std::size_t rowIndex, std::size_t colIndex)
{
assert(rowIndex < ROWS);
assert(colIndex < COLS);
return data[rowIndex][colIndex];
}
Matrix<COLS, ROWS, BackingType> Transpose() const
{
Matrix<COLS, ROWS, BackingType> resu<
for(std::size_t rowIdx = 0; rowIdx < ROWS; rowIdx )
{
for (std::size_t colIdx = 0; colIdx < COLS; colIdx)
{
result.Member(colIdx, rowIdx) = Member(rowIdx, colIdx);
}
}
return resu<
}
template <std::size_t otherRows, std::size_t otherCols>
Matrix<ROWS otherRows, COLS, BackingType> AugmentBelow(const Matrix<otherRows, otherCols, BackingType>amp; other)
{
static_assert(COLS == otherCols, "Columns must match for a vertical augmentation.");
Matrix<ROWS otherRows, COLS, BackingType> resu<
for (std::size_t curRow = 0; curRow < ROWS; curRow)
{
for (std::size_t curCol = 0; curCol < COLS; curCol)
{
result.Member(curRow, curCol) = Member(curRow, curCol);
}
}
for (std::size_t curRow = ROWS; curRow < (ROWS otherRows); curRow)
{
for (std::size_t curCol = 0; curCol < COLS; curCol)
{
result.Member(curRow, curCol) = other.Member(curRow - ROWS, curCol);
}
}
return resu<
}
template <std::size_t otherRows, std::size_t otherCols>
Matrix<ROWS, COLS otherCols, BackingType> AugmentRight(const Matrix<otherRows, otherCols, BackingType>amp; other)
{
static_assert(ROWS == otherRows, "Rows must match for a vertical augmentation.");
Matrix<ROWS, COLS otherCols, BackingType> resu<
for (std::size_t curRow = 0; curRow < ROWS; curRow)
{
for (std::size_t curCol = 0; curCol < COLS; curCol)
{
result.Member(curRow, curCol) = Member(curRow, curCol);
}
for (std::size_t curCol = COLS; curCol < (COLS otherCols); curCol)
{
result.Member(curRow, curCol) = other.Member(curRow, curCol - COLS);
}
}
return resu<
}
static Matrix<ROWS, COLS, BackingType> Identity()
{
static_assert(ROWS == COLS, "Identity matrices are always square.");
Matrix<ROWS, COLS, BackingType> resu<
for (std::size_t diagonal = 0; diagonal < ROWS; diagonal)
{
result.Member(diagonal, diagonal) = 1;
}
return resu<
}
};
template <std::size_t leftRows, std::size_t leftCols, std::size_t rightRows, std::size_t rightCols, typename BackingType>
inline Matrix<leftRows, rightCols, BackingType> operator*(const Matrix<leftRows, leftCols, BackingType>amp; left, const Matrix<rightRows, rightCols, BackingType>amp; right)
{
static_assert(leftCols == rightRows, "Matrix multiplications require that the left column count and the right row count match.");
Matrix<leftRows, rightCols, BackingType> resu<
for (std::size_t i = 0; i < leftRows; i)
{
for (std::size_t j = 0; j < rightCols; j)
{
BackingType curItem = 0;
for (std::size_t k = 0; k < leftCols; k)
{
curItem = left.Member(i, k) * right.Member(k, j);
}
result.Member(i, j) = curItem;
}
}
return resu<
}
template <std::size_t rows, std::size_t cols, typename BackingType>
inline Matrix<rows, cols, BackingType> operator*(BackingType val, const Matrix<rows, cols, BackingType>amp; target)
{
Matrix<rows, cols, BackingType> result = target;
for (std::size_t i = 0; i < rows; i)
{
for (std::size_t j = 0; j < cols; j)
{
result *= val;
}
}
return resu<
}
Ответ №1:
Альтернатива:
template<typename T, int Rows, int Cols>
struct matrix {
template<
// we need to 'duplicate' the template parameters
// because SFINAE will only work for deduced parameters
// and defaulted parameters count as deduced
int R = Rows
, int C = Cols
// C 11 allows the use of SFINAE right here!
, typename = typename std::enable_if<
(R == 1 amp;amp; C == 1)
>::type
>
operator T() const;
};
Комментарии:
1. Если параметры по умолчанию считаются выведенными, вам все равно понадобятся параметры шаблона R и C? Третий параметр также является параметром по умолчанию, верно?
2. @ShinNoNoir чтобы упростить задачу, давайте дадим явное имя этому третьему параметру:
typename Sfinae = typename std::enable_if<…>::type
. в то времяSfinae
как сам по себе (параметр) будет выведен, как вы правильно вывели, аргумент (по умолчанию), который он принимает, будет включать невыведенные параметрыRows
иCols
. Это последняя часть, которой нам нужно избегать.3. Вот демонстрационная программа, которая демонстрирует, как это работает, хорошее упражнение — раскомментировать первую
norededuced
перегрузку и следовать обратному пути создания экземпляра шаблона к месту возникновения ошибки.
Ответ №2:
Вот грубый обходной путь, используя conditional
вместо enable_if
:
#include <functional>
template <typename T, int N, int M>
struct Matrix
{
struct IncompleteType;
T buf[N * M];
operator typename std::conditional<N == 1 amp;amp; M == 1, T, IncompleteType<T>>::type () const
{
return buf[0];
}
};
С некоторой работой, вероятно, можно было бы сделать ошибку компилятора немного более значимой.
Комментарии:
1. Спасибо. Я изо всех сил пытаюсь поместить оператор преобразования внутрь
IncompleteType
со статическим утверждением, чтобы создать хорошее сообщение об ошибке…2. Почему бы просто не поместить статическое утверждение в оператор с тем же условием?
3. Одним из недостатков этого взлома является то
Matrix
, что он больше не может быть создан явно для N!= 1 или M!= 1 (потому что тогда определение функции преобразования будет создано и станет неверно сформированным, поскольку его возвращаемый тип будет неполным). Таким образом, вы можете использовать толькоMatrix
тогда, когда вы только неявно создаете его экземпляр, если используется этот трюк.
Ответ №3:
template <typename T, int N, int M>
struct Matrix
{
T buf[N * M];
operator typename std::conditional<N == 1 amp;amp; M == 1, T, void>::type () const
{
return buf[0];
}
};
В этом случае нет необходимости определять IncompleteType
. void
Достаточно использовать, потому что функция с void
типом не должна возвращать никакого значения, но она возвращает что-то. Это приводит к сбою замены и срабатыванию SFINAE.
Комментарии:
1. Для меня это сработало отлично. Лучшее решение imo.
Ответ №4:
Нет, вы не можете использовать enable_if
с неявными операторами преобразования, нет типов, к которым вы можете его применить. Переместите всю вашу общую функциональность в matrix_base
класс шаблона, а затем унаследуйте от него специализации и добавьте специальную функциональность. Альтернативой является все равно реализовать метод и поместить в него статическое утверждение, чтобы вызвать ошибку компилятора, если его экземпляр создан. Обратите внимание, что это предотвратило бы явное создание экземпляра вашего класса.
Комментарии:
1. 1. Я не ищу enable_if специально — все, что работает аналогично, в порядке. 2. Что вы имеете в виду типы? (СТРОКИ == 1) amp;amp; (COLS == 1) отлично служит константой времени компиляции.
2. @Billy ONeal: если
enable_if
это не обязательно, проверьте ответ Керрека.