Устранить неоднозначность рекурсивного определения функции шаблона - PullRequest
1 голос
/ 23 апреля 2020

Я строю Матрицу статического типа, где все операции с матрицами проверяются по типу. Однако у меня возникают проблемы, когда я хочу сделать что-то, что изменяет Матрицу на основе заданного числа.

Например, добавить один столбец тривиально:

template<int A, int B>
Matrix<A,B+1> addOneCol(Matrix<A,B> m1) {
    return Matrix<A,B+1>();
}

Однако добавление N столбцов это намного сложнее. Поскольку невозможно проверить тип с помощью функции, у которой есть ветвь, в которой тип возвращаемого значения не является ожидаемым (даже если условие ветвления гарантирует это), я могу думать только о рекурсивном подходе:

template<int A, int B, int Z>
Matrix<A,B+1> addZCols(Matrix<A,B> m1) {
    return addOneCol(m1);
}

template<int A, int B, int Z>
Matrix<A,B+Z> addZCols(Matrix<A,B> m1) {
    return addOneCol(addZCols<A,B,Z-1>(m1));
}

template<int A, int B>
Matrix<A,B+1> addOneCol(Matrix<A,B> m1) {
    return Matrix<A,B+1>();
}

Однако это перегружает addZCols в типе возврата, что недопустимо и приводит к ошибке, говорящей о том, что вызов addZCalls является неоднозначным и не может выбрать одного из 2 кандидатов. И я хочу, чтобы версия с B+1 называлась, так сказать, только базовым случаем, когда Z=1.

Любая идея о том, как заставить это работать, или другой подход?

Ответы [ 2 ]

2 голосов
/ 23 апреля 2020

Если я правильно понимаю ваше требование, вы можете просто написать шаблон функции следующим образом:

template<int A, int B, int Z = 1>
Matrix<A,B+Z> addZCols(Matrix<A,B> m1) {
    return Matrix<A,B+Z>{};
}

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

Matrix<1,2> a = addZCols(Matrix<1,1>{});

Matrix<1,4> b = addZCols<1,1,3>(Matrix<1,1>{});

По умолчанию третий параметр 1, и поэтому этот шаблон функции можно использовать как addOneCol.

. Как указывает @Evg, у параметров шаблона есть приятное свойство, что аргументы по умолчанию могут появляться в любом порядке, поэтому мы могли бы иметь аргумент Z в первой позиции:

template<int Z = 1, int A, int B>
Matrix<A,B+Z> addZCols(Matrix<A,B> m1) {
    return Matrix<A,B+Z>{};
}

Это позволяет сделать вызов более удобным, например:

Matrix<1,2> a = addZCols(Matrix<1,1>{});

Matrix<1,4> b = addZCols<3>(Matrix<1,1>{});

Поскольку необходимо указать только Z , поскольку A и B могут быть выведены из аргумента Matrix.

1 голос
/ 23 апреля 2020

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

#include <type_traits>

template <int A, int B>
struct Matrix {
    constexpr int rows() const { return A; }
    constexpr int cols() const { return B; }

    int data;
};

template<int Z, int A, int B, std::enable_if_t<Z == 0, int> = 0>
Matrix<A, B> addZCols(Matrix<A,B> m1) {
    return m1;
}

template<int Z, int A, int B, std::enable_if_t<Z != 0, int> = 0>
Matrix<A,B+Z> addZCols(Matrix<A,B> m1) {
    return addOneCol(addZCols<Z-1, A, B>(m1));
}

template<int A, int B>
Matrix<A,B+1> addOneCol(Matrix<A,B> m1) {
    return Matrix<A,B+1>();
}

int main() {
    Matrix<2, 2> m1;
    auto m2 = addZCols<3>(m1);

    static_assert(m2.rows() == 2, "check rows");
    static_assert(m2.cols() == 5, "check cols");

    return 0;
}

I для ясности также сместил предел рекурсии на единицу и переупорядочил параметры шаблона addZCols, чтобы сделать его более приятным для вызова, но он работает точно так же с вашей оригинальной подписью.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...