Концептуальный полиморфизм в C ++ - PullRequest
0 голосов
/ 22 марта 2019

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

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

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

Так что это мой конкретный пример, я использую идею «концепций», как она представлена ​​в «Языке программирования C ++» Б. Страуструпом, а именно предикаты constexpr, вместо концепций c ++ 20, поскольку мой компилятор не поддержите, пока:

template<typename T>
bool constexpr is_matrix() { ... }

template<size_t N, size_t M, typename T>
class Matrix {
    ...
    template<typename U>
    Matrix& operator+=(const U& v) {
        if constexpr (is_matrix<U>()) {
            // handle matrix case
        } else {
            // handle scalar case
        }
    }
    ...
}

Этот пример взят из моей простой Matrix / Vector lib, которую я использую для изучения программного рендеринга. Моя идея здесь состоит в том, чтобы потребовать тип U , чтобы удовлетворить мою концепцию (поддерживать все необходимые операции, предоставить необходимые псевдонимы типов) вместо того, чтобы требовать, чтобы он был моим типом Matrix, в случае неудачи при проверке он должен обрабатываться как скаляр.

Итак, какие методы можно применить здесь, чтобы сделать этот код более понятным для конечного пользователя, и существуют ли более эффективные способы обеспечения концептуального параметрического полиморфизма, чем constexpr if?

Мое единственное решение этой проблемы, которое я смог придумать, было использование enable_if, например:

    ...
template<typename U, typename = 
    enable_if_t<is_convertible_v<U, T> ||
        (is_matrix<U>() && is_convertible_v<matrix_value_type_t<U>, T>)>>
Matrix& operator+=(const U& v) {
    ...

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

РЕДАКТИРОВАТЬ: После второго размышления о моем решении, я на самом деле мог static_assert в определении, но все еще предоставляя критерии утверждения в объявлении

template<typename U, bool check = is_convertible_v<U, T> || (is_matrix<U>() && is_convertible_v<matrix_value_type_t<U>, T>)>
... {
    static_assert(check, "Type U should be either convertible to T, or being a matrix of convertible values");
}

EDIT2: что может быть улучшено до более читабельного варианта:

...
template <typename U, bool check = std::disjunction_v<
    compatible_type<This, U>,
    compatible_matrix<This, U>,
    compatible_vector<This, U>>>
Matrix& operator+=(const U& v) {
    assert_compatible<check>();
...

1 Ответ

0 голосов
/ 22 марта 2019

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

 Matrix& operator+=(Matrix const&);

 template<typename matrix, typename=enable_if_t<is_matrix<matrix>::value>>
 Matrix& operator+=(matrix const&);

 template<typename scalar, typename=enable_if_t<is_scalar<scalar>::value>>
 Matrix& operator+=(scalar);

 template<typename arg>
 Matrix& operator+=(arg&&)        // catch attempts to add wrong argument type
 {
     static_assert(is_matrix<arg>::value || is_scalar<arg>::value,
         "Matrix::operator+=(arg) requires matrix or scalar argument");
     assert(false);
     return*this;
 }

Вы также можете объявить последний оператор [[noreturn]].

...