Imagine you have a simple 2D Point object with two setters and getters.
template <typename T>
class Point
{
public:
Point(T x, T y);
T getX() const;
T getY() const;
void setX(T x);
void setY(T y);
private:
T _x;
T _y;
};
But I want to work with this class in more 'scriptable-like' syntax. Something like :
auto point = Point<double>(10, 10);
point.x = 20;
point.y = point.x + 10;
You will say, just use a struct with public variable :
template <typename T>
struct Point
{
T x;
T y;
};
Yes, but I want to keep the privacy of the parameters and extend the class with some methods. So another idea is to make a wrapper helper that add operator alias to the setters/getters :
template <typename T, typename Get, Get(T::*Getter)() const,
typename Set, void(T::*Setter)(Set)>
struct ReadWrite
{
ReadWrite(T& ptr) : ptr(ptr) {}
inline void operator= (Set const& rhs)
{
(ptr.*Setter)(rhs);
}
inline Get operator()()
{
return (ptr.*Getter)();
}
private:
T& ptr;
};
OK, I just modify my Point class to do the work :
template <typename T>
class Point
{
public:
Point(T x, T y);
T getX() const;
T getY() const;
void setX(T x);
void setY(T y);
private:
T _x;
T _y;
public:
ReadWrite<Point<T>, T, &Point<T>::getX, T, &Point<T>::setX> x;
ReadWrite<Point<T>, T, &Point<T>::getY, T, &Point<T>::setY> y;
};
By adding some arithmetics operators ( + - * / ), I can use it like that:
auto point = Point<double>(10, 10);
point.x = 20;
point.y = point.x + 10;
Here, point.x is ok in case of operator overloading in the form:
template <typename T, typename V> inline T operator+(ReadWrite<T> const& lhs, V const& rhs) { return lhs() + rhs; }
template <typename T, typename V> inline T operator-(ReadWrite<T> const& lhs, V const& rhs) { return lhs() - rhs; }
template <typename T, typename V> inline T operator*(ReadWrite<T> const& lhs, V const& rhs) { return lhs() * rhs; }
template <typename T, typename V> inline T operator/(ReadWrite<T> const& lhs, V const& rhs) { return lhs() / rhs; }
If I want use this syntax, but without parenthesis on point.x getter :
auto point = Point<double>(10, 10);
auto x = point.x();
I extend the ReadWrite helper with:
template <typename T, typename Get, Get(T::*Getter)() const,
typename Set, void(T::*Setter)(Set)>
struct ReadWrite
{
ReadWrite(T& ptr) : ptr(ptr) {}
inline void operator= (Set const& rhs)
{
(ptr.*Setter)(rhs);
}
inline Get operator()()
{
return (ptr.*Getter)();
}
inline operator auto() -> Get
{
return operator()();
}
private:
T& ptr;
};
Now with no parenthesis:
double x = point.x; // OK, x is my x value (Point).
auto x = point.x; // Wrong, x is my ReadWrite<T> struct.
What is wrong with the overloading of the auto operator?
Thank you very much for your answer.
There is nothing wrong with your class. The problem is how auto deduces types. The thing you have to remember about auto is it basically follows the rules for template argument deduction and the main thing about that is no implicit conversion will be done. This means that in
auto x = point.x;
You say compiler, give me a variable named x that has the type of the initialization expression. In this case point.x is a ReadWrite<Point<T>, T, &Point<T>::getX, T, &Point<T>::setX> so that is the type x gets. The only way to change that is to change what the initialization expression returns.
Unfortunately I'm not sure how you could do that. Proxy objects don't play well with auto type deduction as we pick up their type, not what they are mimicking.
Related
I tried writing a class with operators two different ways. First, I tried it with the operators defined inside the class. Then, I tried it with the operators defined outside the class. Defining the operators outside the class appears to be better because I can take advantage of implicit conversions on the left-hand-operand. It appears to be widely recommended that operators be defined outside the class when possible.
However, when I make the class a template class, implicit conversions on the left-hand-operand no longer work. In fact, implicit conversions on the right-hand-operand also do not work. Am I doing something wrong?
namespace N1 {
template <class T>
class C1 {
public:
double value;
/* implicit */ C1(double value) : value{value} {}
C1(const C1<T>& other) : value{other.value} {}
C1<T>& operator=(const C1<T>& other) {
this->value = other.value;
return *this;
}
// Define operator+ for C1 inline
inline C1<T> operator+(const C1<T>& other) const {
return this->value + other.value;
}
};
template <class T>
class C2 {
public:
double value;
/* implicit */ C2(double value) : value{value} {}
C2(const C2<T>& other) : value{other.value} {}
C2<T>& operator=(const C2<T>& other) {
this->value = other.value;
return *this;
}
};
// Define operator+ for C2 out-of-line
template <class T>
inline C2<T> operator+(const C2<T>& self, const C2<T>& other) {
return self.value + other.value;
}
} // namespace N1
namespace {
using C1 = N1::C1<int>;
using C2 = N1::C2<int>;
// double f1(double x, const C1& y) {
// return (x + y).value; // not expected to work
// }
double f2(const C1& x, double y) {
return (x + y).value; // works
}
double f3(double x, const C2& y) {
// Works when C2 is a class, fails when C2 is a template class
return (x + y).value;
}
double f4(const C2& x, double y) {
// Works when C2 is a class, fails when C2 is a template class
return (x + y).value;
}
} // namespace
my hope is that clients should be able to write code such as
void my_main() {
N1::C2<Anything> x{4};
auto y = x + 2.0;
auto z = 2.0 + x;
}
You can fix C1's first test case by adding the out of line operator+
template<class T>
C1<T> operator+(double d, const C1<T>& c1) {
return c1 + d;
}
to get both test cases to pass:
double f1(double x, const C1& y) {
return (x + y).value; // not expected to work - but now works
}
double f2(const C1& x, double y) {
return (x + y).value; // works
}
The C2 tests: To fix those, you need to make N1::C2<int> a dependent type. You can do that with a bit of SFINAE which considers all N1::C2<T>s, but only accepts it when T is int. Note that it's a problem with the test cases - not with implicit conversion.
template<class T>
std::enable_if_t<std::is_same_v<T,int>, double>
f3(double x, const N1::C2<T>& y) {
return (x + y).value; // implicit conversion works
}
template<class T>
std::enable_if_t<std::is_same_v<T,int>, double>
f4(const N1::C2<T>& x, double y) {
return (x + y).value; // implicit conversion works
}
or using your C2 typedef which makes it consider all T's but only accepts N1::C2<int>:
using C2 = N1::C2<int>;
template<class T>
std::enable_if_t<std::is_same_v<T,C2>, double>
f3(double x, const T& y) {
return (x + y).value; // implicit conversion works
}
template<class T>
std::enable_if_t<std::is_same_v<T,C2>, double>
f4(const T& x, double y) {
return (x + y).value; // implicit conversion works
}
In the comments you say you want the functions to accept all N1::C2<T>s and then it becomes simpler:
template<class T>
auto f3(double x, const N1::C2<T>& y) {
return (x + y).value;
}
template<class T>
auto f4(const N1::C2<T>& x, double y) {
return (x + y).value;
}
My hope is that clients can write code such as ... Is that not possible?
N1::C2<int> x{4};
auto y = x + 2.0;
auto z = 2.0 + x;
Yes, but you then need to add overloads for that:
template <class T>
C2<T> operator+(const C2<T>& lhs, double rhs) {
return lhs.value + rhs;
}
template <class T>
C2<T> operator+(double lhs, const C2<T>& rhs) {
return rhs + lhs; // just swapped the order to use the above
}
Another option, which is how it's commonly done, is to add the operator+= member function:
template<class T>
class C2 {
// ...
C2& operator+=(const C2& other) {
value += other.value;
return *this;
}
};
You could then define the free functions like so:
template <class T>
C2<T> operator+(const C2<T>& lhs, std::convertible_to<C2<T>> auto&& rhs) {
auto rv = lhs;
rv += rhs;
return rv;
}
template <class T, class U>
std::enable_if_t<!std::same_as<std::decay_t<U>*, C2<T>*>, C2<T>>
operator+(const U& lhs, const C2<T>& rhs) {
return rhs + lhs;
}
Demo
Instead of SFINAE as above, you could create a home made concept to avoid ambiguity when adding two C2<T>s:
template <class From, class To>
concept convertible_to_but_is_not =
not std::same_as<std::remove_reference_t<From>, To> &&
std::convertible_to<From, To>;
template <class T>
C2<T> operator+(const C2<T>& lhs, std::convertible_to<C2<T>> auto&& rhs) {
auto rv = lhs;
rv += rhs;
return rv;
}
template <class T> // making sure that lhs is not a C2<T>
C2<T> operator+(convertible_to_but_is_not<C2<T>> auto&& lhs, const C2<T>& rhs) {
return rhs + lhs;
}
Demo
Per Ted's answer:
Implicit conversions are not considered from function arguments during template argument deduction. This problem can be addressed by the addition of two helper functions:
template <class T>
inline C2<T> operator+(double self, const C2<T>& other) {
return self + other.value;
}
template <class T>
inline C2<T> operator+(const C2<T>& self, double other) {
return self.value + other;
}
Consider the following set of classes and the relationship of their operators: We can implement them in two distinct ways. The first where the operators are defined within the class, and the latter where they are defined outside of the class...
template<typename T>
struct A {
T value;
T& operator+(const A<T>& other) { return value + other.value; }
// other operators
};
temlate<typename T>
struct B {
T value;
T& operator+(const B<T>& other) { return value + other.value; }
};
// Or...
template<typename T>
struct A {
T value;
};
template<typename T>
T& operator+(const A<T>& lhs, const A<T>& rhs) { return lhs.value + rhs.value; }
// ... other operators
template<typename T>
struct B {
T value;
};
template<typename T>
T& operator+(const B<T>& lhs, const B<T>& rhs) { return lhs.value + rhs.value; }
// ... other operators
Is there any way in C++ where I would be able to make a single class or struct of operators to where I could simply be able to declare or define them within any arbitrary class C without having to write those same operators multiple times for each class? I'm assuming that the operators will have the same behavior and property for each distinct class that defines them considering that they will all follow the same pattern.
For example:
template<typename T, class Obj>
struct my_operators {
// define them here
};
// Then
template<typename T>
struct A {
T value;
my_operators ops;
};
template<typename T>
struct B {
T value;
my_operators ops;
};
Remember I'm restricting this to C++17 as I'm not able to use any C++20 features such as Concepts... If this is possible, what kind of method or construct would I be able to use, what would its structure and proper syntax look like? If this is possible then I'd be able to write the operators once and just reuse them as long as the pattern of the using classes matches without having to write those operators for each and every individual class...
What about using CRTP inheritance?
#include <iostream>
template <typename T>
struct base_op
{
auto operator+ (T const & o) const
{ return static_cast<T&>(*this).value + o.value; }
};
template<typename T>
struct A : public base_op<A<T>>
{ T value; };
template<typename T>
struct B : public base_op<B<T>>
{ T value; };
int main()
{
A<int> a1, a2;
B<long> b1, b2;
a1.value = 1;
a2.value = 2;
std::cout << a1+a2 << std::endl;
b1.value = 3l;
b2.value = 5l;
std::cout << b1+b2 << std::endl;
}
Obviously this works only for template classes with a value member.
For the "outside the class" version, base_op become
template <typename T>
struct base_op
{
friend auto operator+ (T const & t1, T const & t2)
{ return t1.value + t2.value; }
};
-- EDIT --
The OP asks
now I'm struggling to write their equivalent +=, -=, *=, /= operators within this same context... Any suggestions?
It's a little more complicated because they must return a reference to the derived object... I suppose that (for example) operator+=(), inside base_op, could be something as
T & operator+= (T const & o)
{
static_cast<T&>(*this).value += o.value;
return static_cast<T&>(*this);
}
Taking the answer provided by user max66 using CRTP and borrowing the concept of transparent comparators provided by the user SamVarshavchik within the comment section of my answer, I was able to adopt them and came up with this implementation design:
template<class T>
struct single_member_ops {
friend auto operator+(T const & lhs, T const & rhs)
{ return lhs.value + rhs.value; }
friend auto operator-(T const & lhs, T const & rhs)
{ return lhs.value - rhs.value; }
template<typename U>
friend auto operator+(T const& lhs, const U& rhs)
{ return lhs.value + rhs.value; }
template<typename U>
friend auto operator-(T const& lhs, const U& rhs )
{ return lhs.value - rhs.value;}
};
template<typename T>
struct A : public single_member_ops<A<T>>{
T value;
A() = default;
explicit A(T in) : value{in} {}
explicit A(A<T>& in) : value{in.value} {}
auto& operator=(const T& rhs) { return value = rhs; }
};
template<typename T>
struct B : public single_member_ops<B<T>> {
T value;
B() = default;
explicit B(T in) : value{in} {}
explicit B(B<T>& in) : value{in.value} {}
auto& operator=(const T& rhs) { return value = rhs; }
};
int main() {
A<int> a1(4);
A<int> a2;
A<int> a3{0};
a2 = 6;
a3 = a1 + a2;
B<double> b1(3.4);
B<double> b2(4.5);
auto x = a1 + b2;
auto y1 = a2 - b2;
auto y2 = b2 - a1;
return x;
}
You can see that this will compile found within this example on Compiler Explorer.
The additional templated operator allows for different types: A<T> and B<U> to use the operators even if T and U are different for both A and B provided there is a default conversion between T and U. However, the user will have to be aware of truncation, overflow & underflow, and narrowing conversions depending on their choice of T and U.
For instance, let's say I have a simple 2D vector templated structure:
template <typename T>
struct Vec { T x, y; };
And a generic way to do summation:
template <typename T, typename U>
constexpr auto operator+(const Vec<T>& u, const Vec<U>& v) {
return Vec<decltype(u.x + v.x)>{u.x + v.x, u.y + v.y};
}
But I have to rewrite template <typename T, typename U> for all the other basic operations (-, *, / etc.). I wish I could do something like:
template <typename T, typename U>
{
constexpr auto operator+(const Vec<T>& u, const Vec<U>& v) { /* ... */ };
constexpr auto operator-(const Vec<T>& u, const Vec<U>& v) { /* ... */ };
/* ... */
}
Also, as said in this thread, auto is not permitted when nested within a decl-specifier, which means that the below solution isn't valid (even if it compiles somehow):
constexpr auto operator+(const Vec<auto>& u, const Vec<auto>& v) { /* ... */ }
You can save one template parameter, by defining the operators inline:
template <typename T>
struct Vec {
T x, y;
template<class U> auto operator+(Vec<U> const& v)
{ return Vec<decltype(x + v.x)>{x + v.x, y + v.y};}
template<class U> auto operator-(Vec<U> const& v)
{ return Vec<decltype(x - v.x)>{x - v.x, y - v.y};}
};
If you completely want to avoid writing repetitive code, macros would be a way to avoid that (whether that is good practice, and whether it helps or disturbs understanding the code, is probably a question of taste and context)
I'm not sure how to describe when the type of a template is the struct itself as shown below.
template<typename T> struct Point{};
Point<Point<int>> p;
Is that defined behavior? If so, I don't know the best way to implement it so that I can return a common_type without an error as shown below.
#include <iostream>
template<typename T> struct Point
{
Point() {}
template<typename U, typename V> Point(const U& u, const V& v): x(u), y(v) {}
T x,y;
};
template<typename T, typename U>
inline Point<typename std::common_type<T, U>::type> operator+(const Point<T>& p, const U& n)
{
return {p.x+n, p.y+n};
}
int main() {
Point<int> p;
Point<double> r1 = p + 1.5; //works
Point<Point<int>> p2;
Point<Point<double>> r2 = p2 + 1.5; //error
return 0;
}
The error is:
no match for ‘operator+’ (operand types are ‘Point<Point<int> >’ and ‘double’)
If you want this to work (in my opinion it shouldn't, but it's up to you), you can use decltype(std::declval<T>()+std::declval<U>()) instead of std::common_type<...>.
#include <iostream>
template<typename T> struct Point
{
Point(): x{}, y{} {}
template<typename U, typename V> Point(const U& u, const V& v): x(u), y(v) {}
T x,y;
};
template<typename T, typename U>
inline Point<decltype(std::declval<T>()+std::declval<U>())> operator+(const Point<T>& p, const U& n)
{
return {p.x+n, p.y+n};
}
template<typename T>
std::ostream &operator<<(std::ostream& os, const Point<T>& p)
{
os << "Point<" << typeid(T).name() << ">(x=" << p.x << ", y=" << p.y << ")";
return os;
}
int main() {
Point<int> p;
auto r1 = p + 1.5;
Point<Point<int>> p2;
auto r2 = p2 + 1.5;
std::cout << p << "\n";
std::cout << r1 << "\n";
std::cout << p2 << "\n";
std::cout << r2 << "\n";
return 0;
}
I also added an overload the print out a point. Since the C++ standard has no guarantees on what typeid(T).name() will give you might see something different, but this is what I get:
Point<i>(x=0, y=0)
Point<d>(x=1.5, y=1.5)
Point<5PointIiE>(x=Point<i>(x=0, y=0), y=Point<i>(x=0, y=0))
Point<5PointIdE>(x=Point<d>(x=1.5, y=1.5), y=Point<d>(x=1.5, y=1.5))
Point<i> is Point<int>, Point<d> is Point<double>, Point<5PointIiE> is Point<Point<int>>, and Point<5PointIdE> is Point<Point<double>>. Note that I used auto for r1 and r2 so the types are deduced by the compiler.
Again, it's up to you whether you think this behavior makes sense for your class.
This is defined behaviour. To allow usage in a recursive fashion, you'll need to specialize the std::common_type metafunction on your Point class. Note that normally, defining anything within the std namespace results in undefined behaviour - however, the standard specifically allows you to provide specializations for some std templates for custom types.
#include <iostream>
template<typename T> struct Point
{
Point() {}
template<typename U, typename V> Point(const U& u, const V& v): x(u), y(v) {}
T x,y;
};
// provide custom common_type implementation
namespace std {
template <class T, class U>
struct common_type<Point<T>, U> {
using type = Point<typename std::common_type<T, U>::type>;
};
}
template<typename T, typename U>
inline Point<typename std::common_type<T, U>::type> operator+(const Point<T>& p, const U& n) {
return {p.x+n, p.y+n};
}
int main() {
Point<int> p;
Point<double> r1 = p + 1.5; //works
Point<Point<int>> p2;
Point<Point<double>> r2 = p2 + 1.5; //works
Point<Point<Point<int>>> p3;
Point<Point<Point<double>>> r3 = p3 + 1.5; //works
// and so on
return 0;
}
This doesn't work because there is not a common type between Point<anything> and numeric types. You should of course ask yourself whether you want this to work to start with, and whether you simply shouldn't explicitly handle one level of nesting and stop there.
To support arbitrary nesting, what you'd want is a special case for when Point includes another Point inside of it: these are then "peeled away", since the result type for Point<Point<T>, U> is declared as the result of addition of Point<T> and U.
Finally, I imagine that you're assuming that std::common_type<T,U> results in the same type as T{}+U{}, so that the addition via the Point indirection works just like addition on the most interior fields would work. I don't have a problem there as long as you force the compiler to check that fact.
#include <type_traits>
template<typename T> struct Point
{
constexpr Point() {}
template<typename U, typename V> constexpr Point(const U& u, const V& v): x(u), y(v) {}
T x = {}, y = {};
};
template<typename T, typename U>
inline constexpr Point<typename std::common_type_t<T, U>> operator+(const Point<T>& p, const U& n)
{
static_assert(std::is_same_v<std::common_type_t<T,U>, decltype(std::declval<T>() + std::declval<U>())>);
return {p.x+n, p.y+n};
}
template<typename T, typename U>
inline constexpr Point<decltype(Point<T>{} + U{})> operator+(const Point<Point<T>>& p, const U& n)
{
return {p.x+n, p.y+n};
}
template <class T, class U>
inline constexpr bool operator==(const Point<T> &p, const Point<U> &r)
{
return p.x == r.x && p.y == r.y;
}
int main() {
constexpr Point<int> p;
constexpr Point<double> r1 = p + 1.5;
constexpr Point<Point<int>> p2;
constexpr Point<Point<double>> r2 = p2 + 1.5;
constexpr Point<Point<Point<int>>> p3;
constexpr Point<Point<Point<double>>> r3 = p3 + 1.5;
constexpr Point<Point<Point<Point<int>>>> p4;
constexpr Point<Point<Point<Point<double>>>> r4 = p4 + 1.5;
static_assert(r4.x == p4.x + 1.5);
static_assert(r4.x.x == p4.x.x + 1.5);
static_assert(r4.x.x.x == p4.x.x.x + 1.5);
static_assert(r4.x.x.x.x == p4.x.x.x.x + 1.5);
}
I try to make a generic, but still efficient multi dimension Point class.
What I have is a Dimensions enum
enum Dimension : std::size_t { _2D = 2, _3D = 3 };
And a Point class
template <typename T, Dimension D>
class Point : public std::array<T, D>
{
public:
T& at(size_t idx) { return std::array<T,D>::at(idx); };
const T& at(size_t idx) const { return std::array<T,D>::at(idx); };
Dimension dim() const { return D; }
...
};
I would like to create nices constructors so I added (outside my class definition)
template <typename T>
Point<T,_2D>::Point(T x, T y) { at(0) = x; at(1) = y; }
template <typename T>
Point<T,_3D>::Point(T x, T y, T z) { at(0) = x; at(1) = y; at(2) = z; }
But still I cannot use thoses. The compiler tells me only default (empty) and copy constructors are registered.
Question:
How do I define constructors with arguments list length being dependant on my Dimension template ?
I would do it like this because I'm too lazy to write many versions of the same thing:
template<class T, Dimension D>
class Point : public std::array<T,D> {
template<class... Args>
Point(Args... vs) :
std::array<T,D>{{vs...}}
{
static_assert(sizeof...(Args) == D, "wrong number of args");
}
...
};
There are a few ways to accomplish this. In your case, it might be easiest to use std::enable_if:
template <typename T, Dimension D>
class Point : public std::array<T, D>
{
public:
template <
Dimension D2=D,
typename = typename std::enable_if<D2==_2D>::type
>
Point(T x,T y);
template <
Dimension D2=D,
typename = typename std::enable_if<D2==_3D>::type
>
Point(T x,T y,T z);
T& at(size_t idx) { return std::array<T,D>::at(idx); };
const T& at(size_t idx) const { return std::array<T,D>::at(idx); };
Dimension dim() const { return D; }
...
};
Another option would be to break this into multiple classes and use specialization:
template <typename T, Dimension D>
class PointBase : public std::array<T, D>
{
public:
T& at(size_t idx) { return std::array<T,D>::at(idx); };
const T& at(size_t idx) const { return std::array<T,D>::at(idx); };
Dimension dim() const { return D; }
...
};
template <typename T, Dimension D> class Point;
template <typename T>
class Point<T,_2D> : public PointBase<T,_2D> {
public:
Point(T x,T y);
};
template <typename T>
class Point<T,_3D> : public PointBase<T,_3D> {
public:
Point(T x,T y,T z);
};