1

I am implementing some matrix operations for my templated matrix<T> and matrix_view<T>. At first I had

template <typename T, typename U>
matrix<common_type_t<T, U>> operator+(matrix_view<T>, matrix_view<U>);

and expected it to work also for matrix

matrix<int> a, b;
auto c = a + b;

because there is a converting constructor from matrix<T> to matrix_view<T>.

But it didn't work, because of the "Type deduction does not consider implicit conversions" rule.

So I added another overload

template <typename T, typename U>
matrix<common_type_t<T, U>> operator+(matrix<T>, matrix<U>);

But the story is still not over, for operator+ between matrix<T> and matrix_view<T>, I need to define another two overloads to make them work.

Now I wonder if I have to define all four overloads for every matrix operation. That's a lot of boilerplate code. And indeed the number of overloads needed grows exponentially with the number of types involved, if I am to add some other types in the future.

Is there any solution to this problem? Preferably only requiring me to define the first overload.

Masquue
  • 166
  • 8

2 Answers2

1

Use a concept:

template<typename C>
concept matrix_c = requires(C const& c) { [] <typename ... X>(matrix<X ...> const&) {}(c); }
                || requires(C const& c) { [] <typename ... X>(matrix_view<X ...> const&) {}(c); }

auto operator+(matrix_c auto&& m1, matrix_c auto&& m2);

This solution will work for all combinations of matrix_view and matrix.

Note that the concept can be simplified if you use, e.g., a common base class matrix_base, as it's usual in expression template code. It'll work, however, also for completely unrelated classes.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • Thanks! The concept solution is really interesting, though I think the common base solution is more appropriate here. – Masquue Dec 16 '22 at 21:20
  • @Masquue: if you have a common base class, you could simply pass the parameters by const ref to this class, e.g. `operator+(matrix_base const&, matrix_base const&)`. This is what standard libraries such as Eigen do. – davidhigh Dec 16 '22 at 21:37
  • Wouldn't it be equivalent to check `std::is_convertible`? – AndyG Dec 16 '22 at 22:26
  • @AndyG: you mean `std::convertible` instead of the immeduately invoked lamda expressions (IILE)? Yes, that could work here as well, and probably easier. I just like the IILE, because you can directly include the template arguments. That is, one gets a `std::convertible` on the template template level. – davidhigh Dec 17 '22 at 08:28
0

This is what I decided to be my solution, by traditionally specializing a helper template class.

@davidhigh 's immediately invoked lambda expressions (IILE) solution or the std::convertible semantics might not mean as accurately as what I want here.

// `is_specialization_of` shamelessly copied from this question
// https://stackoverflow.com/questions/70130735/c-concept-to-check-for-derived-from-template-specialization
template <typename T, template<typename...> typename Primary>
struct is_specialization_of : std::false_type {};

template <template<typename...> typename Primary, typename... Args>
struct is_specialization_of<Primary<Args...>, Primary> : std::true_type {};

// somehow I need to relax the condition a little to also allow const T and T&
template <template<typename...> typename Primary, typename T>
struct is_specialization_of<T&, Primary> : is_specialization_of<T, Primary> {};

template <template<typename...> typename Primary, typename T>
struct is_specialization_of<const T, Primary> : is_specialization_of<T, Primary> {};

template <typename T, template<typename...> typename Primary>
inline constexpr bool is_specialization_of_v = is_specialization_of<T, Primary>::value;

template <typename T>
concept matrix_like = is_specialization_of_v<T, matrix> ||
                      is_specialization_of_v<T, matrix_view>;
Masquue
  • 166
  • 8
  • This solution may be overly restrictive. It only allows matrix and matrix_view. You may wish to allow any type that is convertible to a matrix_view – AndyG Dec 19 '22 at 14:11
  • @AndyG Indeed, I intentionally wish to disallow any type that may be implicitly converted to `matrix` or `matrix_view` (and currently I don't have such conversions other than between these two). If something is really "like a matrix" and I want to allow it, then I can add it directly into `matrix_like`. – Masquue Dec 19 '22 at 20:05