I am trying to provide out-of-class definitions of arithmetic operators +-*/
(and in-place +=
etc.) for differently templated types. I read that C++20 concepts is the good way to go, as one could constrain the input/output type to provide only one templated definition, although I could not find much examples of this...
I am using a type-safe vector as base class:
// vect.cpp
template<size_t n, typename T>
struct Vect {
Vect(function<T(size_t)> f) {
for (size_t i=0; i < n; i++) {
values[i] = f(i);
}
}
T values [n];
T operator[] (size_t i) {
return values[i];
}
}
I have a derived class for tensors like so:
// tensor.cpp
template <typename shape, typename T>
struct Tensor : public Vect<shape::size, T> {
// ... same initiliazer and [](size_t i)
}
and I shall also define a derived class for read-only views/slices, overriding operator []
to jump accross strides. I'd like to hard code little more than fmap
and fold
methods inside each class and avoid reproducing boilerplate code as much as possible.
I first had a bit of trouble coming up with a suitable concept for Vect<n,T>
-like classes due to different template parameters, but the one below seems to work:
// main.cpp
template<typename V, int n, typename T>
concept Vector = derived_from<V, Vect<n, T>>
template<int n, typename T, Vector<n, T> V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
int main () {
size_t n = 10;
typedef double T;
Vect<n,T> u ([&] (size_t i) {return static_cast<T>(i) / static_cast<T>(n);});
log("u + u", u);
return 0;
}
Error: template deduction/substitution failed, could not deduce template parameter 'n'
Try 2:
Based on this question I figure out-of-class definition has to be a little more verbose, so I added a couple lines to vect.cpp
.
This seems contrived as it would require (3 * N_operators) type signature definitions, where avoiding code duplication is what motivates this question. Plus I don't really understand what the friend
keyword is doing here.
// vect.cpp
template<size_t n, typename T>
struct Vect;
template<size_t n, typename T>
Vect<n, T> operator + (const Vect<n, T>& lhs, const Vect<n, T>& rhs);
template<size_t n, typename T>
struct Vect {
...
friend Vect operator +<n, T> (const Vect<n, T>& lhs, const Vect<n, T>& rhs);
...
}
Error: undefined reference to Vect<10, double> operator+(Vect<10, double> const&, Vect<10, double> const&)' ... ld returned 1 exit status
I am guessing the compiler is complaining about implementation being defined in main.cpp
instead of vect.cpp
?
Question: What is the correct C++ way to do this? Are there any ways to make the compiler happy eg with header files?
I am really looking for DRY answers here, as I know the code would work with a fair amount of copy-paste :)
Thanks!
template<int n, typename T, Vector<n, T> V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
here, you have to have a way to deduce n
and T
. Your V
does not provide that; C++ template argument deduction does not invert non-trivial template constructs (because doing so in general is Halt-hard, it instead has a rule that this makes it non-deduced).
Looking at the body, you don't need n
or T
.
template<Vector V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
this is the signature you want.
The next step is to make it work.
Now, your existing concept has issues:
template<typename V, int n, typename T>
concept Vector = derived_from<V, Vect<n, T>>
this concept is looking at the implementation of V
to see if it is derived from Vect
.
Suppose someone rewrote Vect
with Vect2
with the same interface. Shouldn't it also be a vector?
Looking at the implementation of Vect:
Vect(function<T(size_t)> f) {
for (size_t i=0; i < n; i++) {
values[i] = f(i);
}
}
T values [n];
T operator[] (size_t i) {
return values[i];
}
it can be constructed from a std::function<T(size_t)>
and has a [size_t]->T
operator.
template<class T, class Indexer=std::size_t>
using IndexResult = decltype( std::declval<T>()[std::declval<Indexer>()] );
this is a trait that says what the type result of v[0]
is.
template<class V>
concept Vector = requires (V const& v, IndexResult<V const&>(*pf)(std::size_t)) {
typename IndexResult<V const&>;
{ V( pf ) };
{ v.size() } -> std::convertible_to<std::size_t>;
};
there we go, a duck-type based concept for Vector
. I added a .size()
method requirement.
We then write some operations on all Vector
s:
template<Vector V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
template<Vector V>
std::ostream& operator<<(std::ostream& os, V const& v)
{
for (std::size_t i = 0; i < v.size(); ++i)
os << v[i] << ',';
return os;
}
fix up your base Vect
a tad:
template<std::size_t n, typename T>
struct Vect {
Vect(std::function<T(std::size_t)> f) {
for (std::size_t i=0; i < n; i++) {
values[i] = f(i);
}
}
T values [n];
T operator[] (std::size_t i) const { // << here
return values[i];
}
constexpr std::size_t size() const { return n; } // << and here
};
and then these tests pass:
constexpr std::size_t n = 10;
typedef double T;
MyNS::Vect<n,T> u ([&] (size_t i) {return (T)i / (T)n;});
std::cout << "u + u" << (u+u) << "\n";
(I used namespaces properly, because I feel icky when I don't).
Note that operator+
is found via ADL because it is in MyNS
as is Vect
. For types outside of MyNS
you'd have to using MyNS::operator+
it into current scope. This is intentional and pretty much unavoidable.
(If you inherit from something in MyNS
it will also be found).
...
TL;DR
Concepts should generally be duck typed , that is depend on what you can do with the type, not how the type is implemented. The code does not appear to care if you inherit from a specific type or template, it just wants to use some methods; so test that .
This also avoids trying to deduce the template arguments to the Vect
class; we instead extract it from the interface.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.