Могу ли я использовать что-то вроде enable_if с неявным оператором преобразования?

#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 это не обязательно, проверьте ответ Керрека.