I just ran into a problem when trying to overload operator float() and operator float() const. I thought I could use both overloads to provide different versions for "do things" and "just read"... but it turns out, that with static instances of the class which contains these overloads I can't.
Boiled down the problem pretty much reduces to this:
// Does some math and should convert to float
struct ToFloat
{
// float conversion here
operator float()
{
cout << "called operator float() of " << s << "\n";
f += 1.0f;
return f;
}
// just get current value here
operator float() const
{
cout << "called operator float() *const* of " << s << "\n";
return f;
}
float f;
std::string s;
};
// Uses a static and a normal member of ToFloat
struct Container
{
// return both instances (with some more math before)
operator float()
{
return s * m;
}
// just provide read access
operator float() const
{
return s * m;
}
static inline ToFloat s { 1.0f, "static" };
ToFloat m { 1.0f, "member" };
};
// Uses the container, but must also provide read-only access
struct Use
{
// Give me operator float() of my container
float get()
{
return c;
}
// Give me operator float() const of my container
float getC() const
{
return c;
}
Container c {};
};
int main()
{
Use u {};
printf("getC() %f \n\n", u.getC());
printf("get() %f \n\n", u.get());
printf("getC() %f \n\n", u.getC());
}
Which produces the following output...
called operator float() of static
called operator float() *const* of member
getC() 2.000000
called operator float() of static
called operator float() of member
get() 6.000000
called operator float() of static
called operator float() *const* of member
getC() 8.000000
I don't really get why the static instance of ToFloat always uses the non-const conversion, even if called from a function declared const? What rule applies here?
The static data member Container::s is simply of type ToFloat. It is always accessed directly, never through an implicit dereference of this. In other words, the container's const operator is effectively this:
operator float() const
{
return Container::s * this->m;
}
From this, it should be obvious that there's no reason for Container::s to be treated as const just because this is a const Container *. If you want it to be treated as const, you have to qualify it explicitly:
operator float() const
{
return std::as_const(s) * m;
}
Related
I am studying operator overloading, there are some parts that are difficult to understand.
See this example code.
class A {
private:
char a;
int b;
double c;
public:
A(char _a = 'a', int _b = 99, double _c = 1.618) :a(_a), b(_b), c(_c){
}
public:
operator char() const {
cout << "operator char() called" << endl;
return this->a;
}
operator int() const {
cout << "operator int() called" << endl;
return this->b;
}
operator double() {
cout << "operator double() called" << endl;
return this->c;
}
};
int main(void) {
A a;
char b = a;
int c = a;
double d = a;
printf("%c\n", b);
printf("%d\n", c);
printf("%f\n", d);
return 0;
}
I made this code to test for type conversion operator and expected that the appropriate function would be called for each type of data.
But the result is..
operator double() called
operator double() called
operator double() called
<-- strange character is gone on board!
1
1.618000
I can not understand why the results are not as follows.
operator char() called
operator int() called
operator double() called
a
99
1.618
Why is double operator called when converting to char and int?
Have a good day! :)
You forgot the const on the double conversion operator:
operator double() const { // <---------------------------
cout << "operator double() called" << endl;
return this->c;
}
};
As in your example a is not const, the double conversion is the best match. If you fix that you get the expected output.
Live example
...some opinion based PS:
I didnt find what the core guidelines say about conversion operators, but if I had to make up a guideline for conversion operators it would be: Avoid them. If you use them, make them explicit. The surprising effects of implicit conversion outweigh the benefits by far.
Just as an example, consider std::bitset. Instead of offering conversion operators it has to_string, to_ulong and to_ullong. It is better to have your code explicit. A a; double d = a; is a little bit mysterious. I would have to look at the class definition to get an idea of what is really going on. On the other hand A a; double d = a.as_double(); can do the exact same thing, but is way more expressive.
Yea so the problem is, that you made all operators const except for the double operator. I am still a bit surprised because this const just means that the operator call does not modify the class members. Still it seems that only the double operator is called for all 3. I would all 3 op's make const and then it will work properly.
If someone has an explanation why this happens, I would also like to know.
Cheers.
operator char() const { // here is const
cout << "operator char() called" << endl;
return this->a;
}
operator int() const { // here is const
cout << "operator int() called" << endl;
return this->b;
}
operator double() { // here is no const
cout << "operator double() called" << endl;
return this->c;
}
int main()
{
CComplex c1(9,9);
CComplex c3;
c3 = 5 + c1; // getting error here
c3.print();
cout << c3;
return 0;
}
CComplex CComplex::operator+(const CComplex &complex)
{
CComplex temp;
temp.m_real = complex.m_real + m_real;
temp.m_imaginary = complex.m_imaginary + m_imaginary;
return temp;
}
You need overload + operator for int type and add some constructors if they are not defined
class CComplex
{
public:
CComplex()
: m_real(0), m_imaginary(0)
{ }
CComplex(double real)
: m_real(real), m_imaginary(0)
{ }
CComplex(double real, double imaginary)
: m_real(real), m_imaginary(imaginary)
{ }
CComplex operator + (const CComplex& complex)
{
CComplex temp;
temp.m_real = complex.m_real + m_real;
temp.m_imaginary = complex.m_imaginary + m_imaginary;
return temp;
}
double m_real;
double m_imaginary;
};
CComplex operator + (const int value, const CComplex& complex)
{
return CComplex(value) + complex;
}
std::ostream& operator << (std::ostream& os, const CComplex& complex)
{
os << "(" << complex.m_real << "," << complex.m_imaginary << ")";
return os;
}
int main()
{
CComplex c1(9,9);
CComplex c3;
c3 = 5 + c1; // no more error here
std::cout << c3;
return 0;
}
Result
(14,9)
I think the correct solution is:
class CComplex {
public:
CComplex();
CComplex(int); // NEW - CONVERSION CONSTRUCTOR
CComplex(double real);
CComplex(double real, double imaginary);
friend CComplex operator + (const CComplex& a, const CComplex& b); // FRIEND - NON-MEMBER FUNCTION
};
Please note these features in the code:
There is a constructor getting one int parameter. This is a casting constructor that the compiler can use for automatic conversion from an int to CComplex.
operator+ is a non-member function (friend keyword makes the function non-member). It slightly differs from the situation when operator+ is a member function (CComplex operator+(CComplex const &b) const).
If you write:
CComplex c1;
5 + c1;
Then for 5 + c1 there is searched an operator+ with parameters (int,CComplex). According to C++ standard there is allowed only one conversion to get proper parameter types (sorry I have no formal quotation). There is a conversion constructor from int to CComplex so there is automatically called this conversion constructor and then operator+(CComplex,CComplex) with proper parameters.
If you didn't have CComplex(int) constructor, then 5 + c1 would not work because there are needed TWO conversion from: int to double and from double to CComplex.
Additionaly, if operator+ was a member function (not friend), it wouldn't work at all. Because according C++ standard there is no possibility of automatic conversion for the left operand. So even conversion constructor CComplex(int) wouldn't help.
Plus: I think that overloading operator+ for types like (int, CComplex) is not a good idea because it would cause explosion of declarations. The same you would need for operator-() and for any order of operands - e.g. (CComplex,int), (CComplex,long), etc... By providing conversion constructor from required types you get all these combinations "automatically" and "for free".
I've got an optional-like class (I can't use optional since it's in C++17). It contains a (possible) value along with a flag indicating if it's valid. I've got an explicit bool operator and a conversion operator to get the value out. The problem is, sometimes C++ will choose the bool operator in an explicitly bool context (an if statement), and other times it won't. Can anyone help me understand this behavior:
#include <algorithm>
#include <stdexcept>
template <typename T>
struct maybe {
maybe() : valid_(false) {}
maybe(T value) : valid_(true) { new (&value_) T(value); }
operator T&&() {
return std::move(value());
}
explicit operator bool() const {
return valid_;
}
T& value() {
if (!valid_) {
throw std::runtime_error("boom");
}
return value_;
}
private:
union {
T value_;
};
bool valid_;
};
int main() {
// fine, uses operator bool()
maybe<std::pair<int,int>> m0;
if (m0) {
std::pair<int,int> p = m0;
(void)p;
}
// throws error, uses operator T&&()
maybe<double> m1;
if (m1) {
double p = m1;
(void)p;
}
}
Whenever you write:
if (x)
That is equivalent to having written:
bool __flag(x);
if (__flag)
This is called a contextual conversion to bool (note that it's direct-initialization, so the explicit conversion function is a candidate).
When we do overload resolution on that construction for m0, there's only one valid candidate: explicit operator bool() const.
But when we do overload resolution on that construction for m1, there are two: explicit operator bool() const and operator double&&(), because double is convertible to bool. The latter is a better match because of the extra const qualification on the bool conversion function, even though we have to do an extra double conversion. Hence, it wins.
Would simply remove operator T&&() from your interface, as it doesn't make much sense for this type.
As soon as T is convertible to bool (double is, std::pair is not) your two operators will match and you'll get an ambiguous call, or one may be a better match for some reason.
You should only provide one of the two operators, not both.
Your operator bool and conversion operator are ambiguous in this design.
In the first context, std::pair<int,int> does not cast to bool
so the explicit bool conversion operator is used.
In the second context, double does cast to bool so the T
conversion operator is used, which returns a double, which then
casts to bool implicitly.
Note in the second context, you are calling std::move which puts value in a valid but undefined state, which leads to undefined behavior when you cast value to double a second time in the if block.
I'd use a named member function to indicate if it is valid, and modify the conversion operator:
#include <algorithm>
#include <stdexcept>
template <typename T>
struct maybe {
maybe() : valid_(false) {}
maybe(T value) : valid_(true) { new (&value_) T(value); }
operator T&&() && { return std::move(value_); } // if rvalue
operator T&() & { return value_; } // if lvalue
operator const T&() const & { return value_; } // if const lvalue
bool valid() const { return valid_; }
T& value() {
if (!valid_) {
throw std::runtime_error("boom");
}
return value_;
}
private:
union {
T value_;
};
bool valid_;
};
int main() {
// fine, uses operator bool()
maybe<std::pair<int,int>> m0;
if (m0.valid()) {
std::pair<int,int> p = m0;
(void)p;
}
// throws error, uses operator T&&()
maybe<double> m1;
if (m1.valid()) {
double p = m1;
(void)p;
}
}
EDIT: The conversion operator should only move from member value_ if the maybe object is an rvalue reference. Using && after a functions signature specializes for this case -- please see Kerrek SB's answer for more information.
If the following test-programm
#include <iostream>
class A {
public:
A() {}
explicit operator bool() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
return true;
}
// explicit operator bool() {
// std::cout << __PRETTY_FUNCTION__ << std::endl;
// return true;
// }
const operator int() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
return 1;
}
operator int() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
return 1;
}
};
int main() {
A a;
if (a) {
std::cout << "bool()" << std::endl;
}
if (a + 0) {
std::cout << "int()" << std::endl;
}
}
is run, the output is
int A::operator int()
bool()
int A::operator int()
int()
and not
bool A::operator _Bool()
bool()
int A::operator int()
int()
what I expected (and what you get if you uncomment the commented parts).
So the question is what are the rules giving the conversion to non-const-int precedence over converting to const-bool?
When performing overload resolution on a reference binding, the less cv-qualified type is preferred. This is discussed in 13.3.3.2p3, with the example given:
struct X {
void f() const;
void f();
};
void g(const X& a, X b) {
a.f(); // calls X::f() const
b.f(); // calls X::f()
}
Note that binding an object to the implicit object parameter of a member function (13.3.1.1.1p2) is a reference binding (13.3.3.1.4).
Conversion operators are treated as member functions (13.3.1.5) for the purposes of overload resolution (13.3p2). Contextual conversion to bool has the semantics of initialization (4p4).
Importantly, any conversion required on the return type of the conversion operator is considered only after considering overload resolution between the conversion operators themselves (13.3.3p1).
The solution is to ensure that all conversion operators have the same const-qualification, especially to scalar type.
what are the rules giving the conversion to non-const-int precedence over converting to const-bool?
Using const member function of a non-const object, requires conversion of a non-const object into const-object. That is why operator int() has a better match over operator bool() const.
To make it a bit clearer, if you would remove int operators, what really happens with the first bool context (first if) is this :
if ( const_cast<const A&>(a).operator bool() ) {
Instead, what happens is this :
if ( a.operator int() )
I am learning templates and operator overloading. I have written some code but I am confused... Let me explain...
template <class T>
class tempType
{
public:
T value;
bool locked;
tempType():locked(false)
{
value = 0;
}
T operator=(T val)
{
value=val;
return value;
}
tempType<T> operator=(tempType<T> &val)
{
value=val.value;
return *this;
}
operator T()
{
return value;
}
};
And I did...
int main(void)
{
tempType<int> i;
tempType<bool> b;
tempType<float> f;
i.value = 10;
i = i + f;
return 0;
}
What code I need to write in order to execute
tempType<T> operator=(tempType<T> &val){}
Also, I why operator T() is required?
Unless you implement move semantics, operator= should always take a const & reference to the source value. It should also return a reference to the modified object.
tempType & operator=(T const & val)
{
value=val;
return * this;
}
operator T is an implicit conversion function which allows any tempType object to be treated as an object of its underlying type T. Be careful when specifying implicit conversions that they won't conflict with each other.
An implicit conversion function usually shouldn't make a copy, so you probably want
operator T & ()
{
return value;
}
operator T const & () const
{
return value;
}
Given these, you shouldn't need another overload of operator = because the first overload will simply be adapted by the conversion function to a call such as i = b;.
If a series of conversions will result in the operator=(T const & val) being called, you should avoid also defining operator=(tempType const & val) because the overloads will compete on the basis of which conversion sequence is "better," which can result in a brittle (finicky) interface that may refuse to do seemingly reasonable things.
I think I know all the answers so I might as well post full response.
To override default operator= you should declare it as tempType<T> operator=(const tempType<T> &val){}. Now you need to call the method explicitly via i.operator=(other_i).
If you correct the declaration you can use it like this:
tempType<int> i;
tempType<int> other_i;
i = other_i; // this is what you just defined
The operator T() is called a conversion operator. It is kind of reverse or counter part of the conversion constructor which in your case would be tempType(const &T value).
It is used to convert a class object into a given type. So in your case you would be able to write:
tempType<int> i;
int some_int;
some_int = i; // tempType<int> gets converted into int via `operator int()`
template <class T>
class tempType
{
public:
T value;
bool locked;
tempType() : value(), locked(false)
{
value = 0;
}
//althought legal, returning something different from tempType&
//from an operator= is bad practice
T operator=(T val)
{
value=val;
return value;
}
tempType& operator=(const tempType &val)
{
value=val.value;
return *this;
}
operator T()
{
return value;
}
};
int main(void)
{
tempType<int> a;
tempType<int> b;
a = b;
return 0;
}
in the code, a = b calls the operator.
As for the second question, the operator T() is not needed "by default". In your example, it is used when you write i+f:
i is converted to an int
f is converted to a float
the operation (+) is performed
T tempType<int>::operator=(T val) is called for the assignement