Can/should an overloaded operator have to be inlined to gain better efficiency (wrt time or whatever) if that operator will have to be used frequently?
I want to overload the '+' operator to add big vectors very frequently in my code. Hence the question.
Ideally, you'd profile your code and then decide what to inline. There really isn't much of a difference between when you decide to inline regular operators, over overloaded ones.
If you are adding big vectors, then the overhead of a function call to the plus will be small relative to the time to actually add the two vectors. So marking the operator+ inline is not likely to improve your overall run time.
Let the compiler to decide about optimization.
The keyword inline is misleading: the compiler can -in fact- always do what it needs, just like with the old auto (do you remenber those days?) and register.
It's modern meaning is "defined in header: discard if not used, merge if seen more times".
The compiler should inline smallish functions for you automatically in release builds.
Much more important is to define a move constructor and move assignment. If your arrays are very large and you're doing multiple operations at the same time, you can also use expression classes to improve execution speed.
template <class left, class right>
struct AddExpr {
const left& _left;
const right& _right;
AddExpr(const left& Left, const right& Right)
:_left(Left), _right(Right)
{assert(left.count() == right.count());}
int count() const {return _left.count();}
int operator[](int index) const {return _left[i]+_right[i];}
};
class Array {
int* data;
int size;
int count() const {return size;}
Array& operator=(AddExpr expr) {
for(int i=0; i<expr.count(); ++i)
data[i] = expr[i];
};
AddExpr operator+(const Array& lhs, const Array& rhs)
{return AddExpr<Array, Array>(lhs, rhs);}
AddExpr operator+(const Array& lhs, const Expr& rhs)
{return AddExpr<Array, Expr>(lhs, rhs);}
AddExpr operator+(const Expr& lhs, const Array& rhs)
{return AddExpr<Expr, Array>(lhs, rhs);}
AddExpr operator+(const Expr& lhs, const Expr& rhs)
{return AddExpr<Expr, Expr>(lhs, rhs);}
int main() {
Array a, b, c, d;
Array c = (a+b) + (c+d); //awesome on lines like this
}
This removes all the temporary objects, and greatly improves cache efficiency. But I've completely forgotten what this technique is called.
Related
Consider a class SomeClass:
class SomeClass{
public:
// Constructors, other members
float& operator[](const unsigned i);
friend bool operator==(const SomeClass &A, const SomeClass &B);
};
Suppose this is how the == operator is overloaded for this class (not the actual implementation, but an overly simplified version):
bool operator==(const SomeClass &A, const SomeClass &B){
if (A[0] == B[0])
return true;
return false;
}
This would throw a compiler error, since the overloaded [] operator requires the instance to be non-const. However, if I change the definition of the [] operator to allow for const instances, I can no longer do assignment:
// ASSUMING: const float& operator[](const unsigned i) const;
SomeClass a;
a[0] = 0; // error, because the return value of [] is a reference to const!
I really don't want to drop the const in the parameters of the == operator, since the operands don't change within the function. What is the right way to deal with this issue?
Overload operator [] to provide both:
float& operator [](unsigned int i);
float operator [](unsigned int i) const;
For a generic T that’s not cheap to copy, use a T const& return value. The general pattern for implementing read/write operator [] is
T& operator [](index_type i);
T const& operator [](index_type i) const;
I started learning C++ not long ago and I've got an issue.
I know that a lot of similar questions has been asked about this topic.
However, I didn't deeply understand something which is why I am here.
So, my question is:
why do I need to provide 2 versions of the [] operator?
isn't the const version sufficient?
For example, I have been working on an Array class: (The last 2 operators are the relevant ones)
class Array
{
private:
int* _arrPtr;
int _len;
public:
friend ostream& operator<<(ostream& out, const Array& other);
friend istream& operator>>(istream& in, Array& other);
Array(int len = 10, int val = 0);
Array(const Array& other);
~Array();
void setArray(int len);
int* getArray() const;
void setLen(int len) { this->_len = len; }
int getLen() const { return this->_len; }
Array operator+(const Array& other) const;
Array& operator=(const Array& other);
Array operator+(int val);
int& operator[](int index) const;
int& operator[](int index);
};
can I replace the two operators
int& operator[](int index) const;
int& operator[](int index);
only with the const one?
int& operator[](int index) const;
Wouldn't it be the same??? wouldn't it be the same for any operator overloading if the there is only one const version of the operator?(under the assumption that all of the const methods do have the word 'const' at the end of their declaration)
Thank you very much!!!
Whether or not you need two overloads depends on what interface you want to provide.
If you want to be able to modify elements even in const instances of Array, then yes, the single int& operator[](int index) const; is enough. This is what std::unique_ptr does.
But if you want elements of const Arrays to be read-only, and at the same time elements of non-const Arrays to be mutable, you need two overloads. std::vector does that.
Usually the second option is preferred.
You cannot write int& operator[](int index) const;. Instead, you need to write:
const int& operator[](int index) const;
int& operator[](int index);
Why is this so? Because if something is const, you can have only const references to it or it's elements, as non-const referenes whould let you change it, which you have no right to.
If you want const references when the thing is const and non-const references in case the thing is not const, you need the overloading. If you only want const references in any case, you may get without the overloading.
class Vector{
......
.......
private:
int dim;
public:
int getDim() {
return this->dim;
}
const Vector operator+(const Vector& right){
this->getSize();
}
};
And I got compile error in this->getSize();. It is caused fact, that argument right is const. I don't know where is problem. I don't try modify right.
Presumably you have a non-const method Vector::getSize(). You need to make it const so that it can be called on const objects or via const references or pointers to const. For example:
int getSize() const;
^^^^^
Also note that it doesn't make much sense to return a const value (and would inhibit move semantics if you had them). The canonical form of an addition member operator would be
// const method: A = B + C should not modify B
Vector operator+(const Vector& right) const;
^^^^^
and the non-member
Vector operator+(const Vector& left, const Vector& right);
The following code will fail to compile under GCC because it does define operator== but does not define operator!=.
struct A {
unsigned int m_i;
bool operator == (const A& rhs) const { return m_i == rhs.m_i; }
};
bool f(const A& lhs, const A& rhs) { return lhs != rhs; }
Obviously it wants either
bool operator != (const A& rhs) const { return !(operator==(rhs)); }
or
bool operator != (const A& rhs) const { return m_i != rhs.m_i; }
Common wisdom seems to be that this is because !operator== adds an instruction and so is less efficient. This leads some programmers to dutifully write out their complex != expression in full, and over the years I've fixed a number of bugs resulting from mismatched operators.
Is this coercion to write both operators a case of premature/legacy optimization, or is there a good, solid, practical reason to do this code-doubling that I'm just somehow missing ?
I would say absent some overwhelming evidence to the contrary, it's purely premature optimization (not even legacy--I doubt there was ever a good reason for it, at least in anything approaching a C++ time-frame).
For what it's worth, §20.2.1 of the C++ standard defines a number of overloads in <utility> that will give you a != based on operator== and a >, >=, <= all based on operator<.
Why not use this:
bool f(const A& lhs, const A& rhs) { return !(lhs == rhs); }
For example: in a C++ header file, if I defined a struct Record and I would like to use it for possible sorting so that I want to overload the less operator. Here are three ways I noticed in various code. I roughly noticed that: if I'm going to put Record into a std::set, map, priority_queue, … containers, the version 2 works (probably version 3 as well); if I'm going to save Record into a vector<Record> v and then call make_heap(v.begin(), v.end()) etc.. then only version 1 works.
struct Record
{
char c;
int num;
//version 1
bool operator <(const Record& rhs)
{
return this->num>rhs.num;
}
//version 2
friend bool operator <(const Record& lhs, const Record& rhs) //friend claim has to be here
{
return lhs->num>rhs->num;
}
};
in the same header file for example:
//version 3
inline bool operator <(const Record& lhs, const Record& rhs)
{
return lhs->num>rhs->num;
}
Basically, I would like to throw the questions here to see if someone could come up with some summary what's the differences among these three methods and what are the right places for each version?
They are essentially the same, other than the first being non-const and allowing you to modify itself.
I prefer the second for 2 reasons:
It doesn't have to be a friend.
lhs does not have to be a Record
The best way to define the less operator is:
struct Record{
(...)
const bool operator < ( const Record &r ) const{
return ( num < r.num );
}
};
Welcome to c++20 where we have even more options.
//version 1
bool operator <(const Record& rhs)
{
return this->num>rhs.num;
}
this one is wrong, it should read:
//version 1
bool operator <(const Record& rhs)const
{
return this->num>rhs.num;
}
as you want the left hand side to be const-qualified as well.
//version 2
friend bool operator <(const Record& lhs, const Record& rhs) //friend claim has to be here
{
return lhs->num>rhs->num;
}
this one is symmetric. So suppose you have a struct Bar with an operator Record.
Then
Record rhs;
Bar lhs;
assert( lhs < bar );
the above works with a symmetric case, but not with a member function version.
The friend in class version is an operator that can only be found via Koenig lookup (Argument Dependent Lookup). This makes it very useful for when you want a symmetric operator (or one where the type is on the right, like ostream&<<*this) bound to a specific template class instance.
If it is outside of the class, it has to be template function, and a template function does overloading differently than a non-template function does; non-template functions permit conversion.
template<class T>
struct point {
T x ,y;
point operator-(point const& rhs)const{
return {x-rhs.x,y-rhs.y};
}
friend point operator+(point const& lhs, point const& rhs) {
return {lhs.x+rhs.x, lhs.y+rhs.y};
}
};
template<class T>
point<T> operator*( point<T> const& lhs, point<T> const& rhs ) {
return {lhs.x*rhs.x, lhs.y*rhs.y};
}
here - is asymmetric, so if we have a type that converts to a point<int> on the left, - won't be found.
+ is symmetric and a "Koenig operator", so it is a non-template operator.
* is symmetric, but is a template operator. If you have something that converts-to-point, it won't find the * overload, because deduction will fail.
//version 3
inline bool operator <(const Record& lhs, const Record& rhs)
{
return lhs->num>rhs->num;
}
this is similar to the template above, but here that problem doesn't occur. The difference here is that you can get the address of this function outside of the class, while the "koenig operator<" you wrote can only be found via ADL. Oh, and this isn't a friend.
c++17 adds in
auto operator<=>(const Record&)=default;
where we use the spaceship operator <=> to define ordering automatically.
This will use the ordering of both c and num to produce the required result.
Much like the rule of 5, you should seek to make =default here work correctly. Having state that < ignores is a bad smell, and so is entangling different parts of your state.
The non-member equivalent of your member function
bool operator <(const Record& rhs);
is
bool operator <(Record& lhs, const Record& rhs); // lhs is non-const
Now STL containers treat the items they store as const (at least as long as the comparison operator is concerned). So they call const-const variant of your operator. If they don't find it (in case you provided variant 1 only) - it is a compile error.
If you provide both const-const member and const-const non-member:
struct Record
{
bool operator <(const Record& rhs) const;
};
bool operator <(Record& lhs, const Record& rhs);
it is yet another compiler error because such definition leads to an ambiguity:
If two matches are found at the highest level where a match is found,
the call is rejected as ambiguous. /Stroustrup, C++, section 12.3.1/
Finally, (as noted in the previous answers) there's no need in the friend modifier since by default all fields of the struct are public.
PS make_heap doesn't expect the compared items to be const because it is a more low-level beast and using it you're kind of co-authoring a new heap-based library so it is your responsibility to track const-ness of items.
PPS set treatment of items as const does not protect you from modifying the keys of the items after they are inserted into the container - it will result in a runtime error (segfault) if you try it.
Favor in-class unless it cannot be in-class because first argument is the wrong type.