As title, can be to overloading operator = for casting?
I have a simple class.
class A{
protected:
int m_int;
public:
A& operator=( int& obj)
{
m_int = obj;
return *this;
}
};
I want:
A t_a = 1;
and
int t_int = t_a;
Is there a way to do this?
Just define conversion operator
operator int() const
{
return m_int;
}
or
explicit operator int() const
{
return m_int;
}
In the last case you have to use an explicit casting in the statement
int t_int = int( t_a );
Take into account that the assignment operator should be declared like
A& operator=( const int& obj)
{
m_int = obj;
return *this;
}
or like
A& operator=( int obj)
{
m_int = obj;
return *this;
}
Otherwise it will be impossible to bind the non-constant reference with integer literals or temporary values.
As for the assignment operator then you may define only a compound assignment operator for the type int and the type A.
For example you could define the operator += or something other operator.
Yes, that’s possible. You need a custom ctor and assignment operator. But writing those disables some of the compiler generated ctors/assignment ops. If you still need/want them, you need to reintroduce them explicitly.
class A
{
protected:
// Note the initializer. Without it m_int is uninitialized
// when you default construct an A. That’s a common source
// of bugs.
int m_int = 0;
public:
// Following two are the important ones
A(int i) : m_int(i) {}
A& operator=(int i)
{
m_int = i;
return *this;
}
// if A needs to be default constructible as well
A() = default;
// The int ctor/assignment disable the compiler generated
// normal copy ctor/assignment (the ones that take another A).
// Reintroduce them like this:
A(const A&) = default;
A& operator=(const A&) = default;
// Writing any copy ctor/assignment disables the compiler generated
// move ctor/assignment. If you still want them, reintroduce them.
A(A&&) = default;
A& operator=(A&&) = default;
};
A t_a = 1;
This doesn't use assignment. You need a constructor which takes an int argument.
int t_int = t_a;
You will need operator int() for this.
Note that it is a really bad idea to have a class which has both an implicit constructor from a type, and an implicit cast to the type. You will get all sorts of confusing errors when you try to do overload resolution.
Instead, I would make the constructor explicit, and write an explicit conversion function. That means you have to write:
int t_int = t_a.to_int();
But at least it's explicit.
Edit: Note that you can overload operator = for casting (either inside or outside the class), but neither of the code samples you gave will use it. = is used both for assignment and initialization, and both your samples are initialization (so won't use operator =)
Related
This is in c++, using Visual Studio 2019 (haven't tried other compilers).
I want to add a templated operator= method. If the parameter is non-const, it works fine. But if the parameter is const, even if I make a version with a const parameter, it isn't called. Instead, it does a simple shallow copy.
If I use a named function instead of the operator, it works as expected. Similarly, if it's not templated, the operator is called as expected. The combo seems to be the issue.
Here's an example that exhibits the issue.
class CTest
{
public:
int x{};
CTest() = default;
CTest(int value) : x(value) {}
// non-const operator=
template<class SrcType>void operator=(SrcType& src)
{
x = src.x;
}
// const operator=
template<class SrcType>void operator=(const SrcType& src)
{
x = src.x;
}
};
int main()
{
CTest nonConstSrc{ 3 };
const CTest constSrc{ 5 };
CTest result;
result = nonConstSrc; // correctly calls non-const operator=
result = constSrc; // ? shallow copy, not calling const operator=
return 0;
}
Any ideas how to get it to use my overloaded function? Thanks.
Your const function template is not called because compiler generated default copy assignment operator by default, which has signature operator=(const CTest&). When compiler must choose between non-template function and template one (when both has the same match), the former is preferred. That is why your templated method is not called.
To help compiler to select version you want, add:
CTest& operator=(const volatile CTest&) = delete;
by above, you disable normal operator=, volatile here is imporant, without it compiler will complain that operator= is disabled. By adding volatile, you just make that template version is better matched than volatile one.
The rest is not changed:
template<class SrcType>
void operator=(SrcType& src)
{
x = src.x;
puts("boom1");
}
template<class SrcType>
void operator=(const SrcType& src) {
x = src.x;
puts("boom2");
}
Demo
If a class does not have a copy-assignment declared, the compiler will generate one implicitly.
You class does not have a copy-assignment operator, it only has a template assignment operator.
When called with a const object the implicitly declared assignment operator is a better match during overload resolution.
The issue here is that we can't delete the implicitly declared assignment operator, since it will then generate a compiler error. We can however write our own assignment operator that forwards to our template.
#include <iostream>
class CTest
{
public:
int x{};
CTest() = default;
CTest(int value) : x(value) {}
CTest(const CTest& v) = delete;
CTest& operator=(const CTest& v) {
return operator=<CTest>(v);
}
// non-const operator=
template<class SrcType>CTest& operator=(SrcType& src)
{
x = src.x;
std::cout << "non-const\n";
return *this;
}
template<class SrcType>CTest& operator=(const SrcType& src)
{
x = src.x;
std::cout << "const\n";
return *this;
}
};
int main()
{
CTest nonConstSrc{ 3 };
const CTest constSrc{ 5 };
CTest result;
result = nonConstSrc; // correctly calls non-const operator=
result = constSrc; // calls copy-assignment and forwards to template
return 0;
}
Let's say I have the following minimal example class:
#include <iostream>
class Foo {
public:
Foo() = default;
Foo(const Foo&) = default;
Foo(Foo&&) noexcept = default;
Foo& operator=(const Foo& rhs) {
std::cout << "copy\n";
return *this;
}
Foo& operator=(Foo&& rhs) noexcept {
std::cout << "move\n";
return *this;
}
Foo operator+(const Foo& rhs) const {
Foo x; // with some calculation
return x;
}
};
int main() {
Foo a, b, c;
a = b + c;
}
This prints move as expected. Now according to Effective C++ Item 3, I should return const Foo from operator+ to avoid construct like a + b = c, i.e.:
// To avoid a + b = c
const Foo operator+(const Foo& rhs) const {}
Unfortunately, this suddenly starts calling copy assignment instead of move assignment operator. [I'm using gcc 4.8.4 on Ubuntu, but it is probably nothing related to compiler]
How can I ensure that a + b = c fails to compile and in the same time move assignment is called for a = b + c? Or with the introduction of move semantics, is there no way to achieve both of them in the same time?
I have ended up using lvalue reference qualifier as pointed by Caninonos in comment and by max66 in now deleted answer (but 10k users can see it).
Foo& operator=(const Foo& rhs) & {}
Foo& operator=(Foo&& rhs) & noexcept {}
It is simple to implement and it provides a better interface design since assignment to anything other that lvalue doesn't sound meaningful and a possible source of bug.
However, it should be noted that the possibility of writing a + b = c by mistake is very low. Also compiler generated assignment operators are not lvalue reference qualified and we can write a + b = c with standard types, e.g. with std::string or with std::complex.
So I am trying to do a program that is simply overloading many operators, but for some reason whenever I try to overload the assignment operator, I get an error that says
error: conversion from 'int' to non-scalar type 'Foo' requested
class Foo {
int value;
public:
operator int() const;
Foo& operator=(const int &val) { value = val; return this; }
...
};
int main()
{
Foo a = 8, b = 9;
...
return 0;
}
I have also tried it without the operator= statement and without the operator int() const; statement, but I just can't seem to get it to compile.
You've confused assignment with initialization.
Foo f = 1; //initialization
f = 2; //assignment
You need to make a constructor that accepts an int too.
Foo(int i) : value(i) {}
//main
Foo f = 1; //uses constructor
Being that it is a single argument constructor (converting constructor), if you don't wish int to be implicitly convertible to Foo, you should make the constructor explicit.
It seems that adding a default constructor prevents from calling emplace_back and produces the error message: "static assertion failed: type is not assignable" (gcc 5.3 with -std=c++14). Here is a simple code that illustrates the issue:
class A {
public:
int a;
A() = default;
A(int a) {
this->a = a;
}
A(A const & a) = delete;
A& operator =(A const & a) = delete;
A(A && a) = default;
A& operator =(A && a) = default;
};
int main() {
A a(4);
std::vector<A> vec;
vec.emplace_back(std::move(a)); // Error: type is not assignable
return 0;
}
When removing the default constructor, the error goes away! Also, if the default constructor is defined (even if it does nothing), the error also goes away:
class A {
public:
int a;
A() {
}
A(int a) {
this->a = a;
}
A(A const & a) = delete;
A& operator =(A const & a) = delete;
A(A && a) = default;
A& operator =(A && a) = default;
};
int main() {
A b;
A a(4);
std::vector<A> vec;
vec.emplace_back(std::move(a)); // Error gone
return 0;
}
It seems that "A() = default;" is what is causing the problem.
Is this normal behaviour on part of the compiler or is it a bug?
It's a libstdc++ bug (edit: reported as bug 69478).
Briefly, libstdc++'s std::vector, as relevant here, uses std::uninitialized_copy (paired with move iterators) to move elements on reallocation, which is reduced to std::copy if the type is trivial and the iterators' reference types are assignable (i.e., the assignment operator that would conceptually be used is usable).
Then, the std::copy for pointers to trivial types (or in our case, a move_iterator wrapping a pointer) is in turn optimized into a call to memmove coupled with a check for is_copy_assignable. Of course, that check is wrong in this case, since the uninitialized_copy, paired with move iterators, only requires the thing to be move constructible.
When you don't have a default constructor or if the default constructor is user-defined, then the class isn't trivial, so you don't hit the code path that triggers this bug.
It seems that initalizer lists are a good idea for your class constructors and, I'm assuming, for the copy constructor as well. For the assignment operator one has to assign each member in the body of the function. Consider the following simple block:
class Foo {
private:
int a,b;
public:
Foo(int c, int d) : a(c), b(d) {}
Foo(const Foo & X) : a(X.a), b(X.b) {}
Foo& operator=(const Foo& X) {
if (this == &X) return *this;
a = X.a;
b = X.b;
return *this;
}
};
If a class has a moderate amount of data members, there are three places where one can mess up the the different assignments/initialization. By that I mean, what if the copy constructor looked like:
Foo(const Foo & X) : a(X.a), b(X.a) {}
or a line was missing from the operator=. Since the assignment operator and the copy constructor often have the same effect (in that we copy members from one Foo to another) can I "reuse" the code from the copy constructor or the assignment operator or vice versa?
Your goal should be to not write copy constructors/assignment operators at all. Your goal should be to let the compiler do it. Standard library containers are all copyable, so use them where reasonable.
If there are members that cannot be copied correctly, then use smart pointers or other RAII objects. Those objects are the ones that should need special copy constructors/assignments. And they only need them for their one member.
Everything else should not use them.
Since the assignment operator and the copy constructor often have the same effect.
Not at all, one does initialization while the other does assignment. They are different in the initial state of the object, and their tasks are separate (though similar). The canonical assignment operator is usually done as:
Foo& operator=(Foo right) {
right.swap( *this );
return *this;
}
It might not be valid to forward all to an assignment operator, but that is was commmon in C++03 where it was allowed.
In C++11 constructors are easier: forward all the constructors to one master constructor.
class Foo {
private:
int a,b;
public:
Foo(int c, int d) : a(c), b(d) {}
Foo(const Foo & X) : Foo(x.a, x.d) {}
//syntax may be wrong, I don't have a C++11 compiler
Foo& operator=(const Foo& X) {
if (this == &X) return *this;
a = X.a;
b = X.b;
return *this;
}
}
In C++03 (where it is allowed)
class Foo {
private:
int a,b;
void init(int c, int d) {a=c; b=d;}
public:
Foo(int c, int d) : {init(c,d);}
Foo(const Foo & X) : {init(X.a, X.b);}
Foo& operator=(const Foo& X) { init(X.a, X.b);}
}
But keep in mind that this cannot be used in some common cases. (any object that isn't assignable)