issue with template operator= with const parameter - c++

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;
}

Related

Does program fail because class lack copy ctor or proper assignment operator?

I'm struggling to understand the exact reason the program fails.
Regarding the following program:
#include <iostream>
template < class T, size_t SIZE>
class Stack {
T arr[SIZE] = {};
int pos = 0;
public:
Stack & push(const T & t) {
arr[pos++] = t;
return *this;
}
Stack & push(T && t) {
arr[pos++] = std::move(t);
return *this;
}
T pop() {
return std::move(arr[--pos]);
}
};
class Foo {
std::string s;
public:
Foo(const char* s = "#") : s(s) {}
Foo(Foo && foo) : s(std::move(foo.s)) {}
Foo & operator=(Foo && foo) {
s = std::move(foo.s);
return *this;
}
void print() const { std::cout << s << std::endl; }
};
int main() {
Stack<std::string, 5> s1;
s1.push("hi").push(std::string{ "bye" });
std::cout << s1.pop() << std::endl;
Stack<Foo, 5> s3;
Foo f2;
s3.push(f2);
}
The program fails at s3.push(f2); is it because Foo doesn't have a copy ctor or is it because its assignment operator function only handles Foo&& types?
I'm suspecting it's the assignment operator function but I'm not sure it's not because of copy ctor as well.
Because you provided a custom move cosntructor and assignment operator, the compiler no longer generates default copy constructor and assignment operator.
You either need to write those too, or, even better, remove the custom move operations.
The compiler will then generate all 4 for you, and they might be better than your manually written ones (e.g. one problem is that you forgot noexcept on move operations, so standard containers will prefer making copies to moves in some scenarios).
Well, the compiler tells you:
error: use of deleted function ‘Foo& Foo::operator=(const Foo&)’
So you can solve it by adding that function:
Foo& operator=(const Foo& foo) {
s = foo.s;
return *this;
}
There are 2 solutions (I will not point out design issues assuming you are just experimenting).
As a internal data use vector<T> instead of the array of fixed size and in the push that accepts the const& move and use emplace_back (nasty and amoral, also it will leave the src in invalid state).
Provide copy constructor to Foo (this is much more easier)

Returning const value from arithmetic operator overload with move assignment

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.

overloading operator = for casting

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 =)

Compiler won't use copy assignment instead move?

I have a class where the move assignment is explicit deleted, since the object should not be moveable. But if i assign to an instance of this class using RVO the compiler gives me the error:
main.cpp:12:16: note: candidate function has been explicitly deleted
also the compiler is mentioning the existing copy assignment operator but does not use it.
here is my code (or a (not) running example here):
class foo {
public:
foo() {}
foo(foo const& r) {}
foo(foo&&) = delete;
foo const& operator=(foo const& r) { return *this; }
foo const& operator=(foo&& r) = delete;
};
int main(int argc, char **argv) {
foo bar;
bar = foo();
return 0;
}
I found a quite similar post here.
I know I can avoid this by using a temporary. i wonder why every compiler (i tested this with gcc, clang and vs2013) is not able to call the existing copy assignment directly? Is there something I am missing?
The copy assignment is not called, because the (deleted) move assignment is a better match for overload resolution.
Simply don't declare the move assignment at all. Then the copy assignment will be selected. The implicit move assignment operator won't be generated because the class has a user declared copy constructor, move constructor and copy assignment operator. Any of those will prevent the generation of the implicit move assignment operator.
But if i assign to an instance of this class using RVO
There is no RVO involved here. You create a temporary foo and copy assign it to an existing variable. Copy assignments cannot be elided.
Also, it's quite unusual and inefficient to return by value from an assignment operator.
Just like std::move() applies a static_cast<>() to force the use of the move assignment/constructor, one can do something similar to force the use of the copy assignment/constructor:
#include <iostream>
class foo
{
public:
foo() {}
foo(foo const& r) {}
foo(foo&&) = delete;
foo const& operator=(foo const& r) { std::cout << ":)\n"; return *this; }
foo const& operator=(foo&& r) = delete;
};
int main(int argc, char **argv)
{
foo bar;
bar = static_cast<const foo&>(foo());
return 0;
}
You could use placement new to do this:
#include <iostream>
#include <string>
#include <new>
class foo {
public:
foo() {}
foo(foo const& r) {}
foo(foo&&) = delete;
foo const& operator=(foo const& r) { return *this; }
foo const& operator=(foo&& r) = delete;
};
int main(int argc,char **argv)
{
foo bar;
//bar = foo();
bar.~foo();
new(&bar) foo();
return 0;
}

Should templates make non-Rvalue-reference constructors/assigns for move only parameters of different type?

Let us say I have a move-only type. We stop the default-provided constructors from existing, but Rvalue references introduce a new "flavor" we can use for the moving-versions of the signatures:
class CantCopyMe
{
private:
CantCopyMe (CantCopyMe const & other) = delete;
CantCopyMe & operator= (CantCopyMe const & other) = delete;
public:
CantCopyMe (CantCopyMe && other) {
/* ... */
}
CantCopyMe & operator= (CantCopyMe && other) {
/* ... */
}
};
I recently thought you were always supposed to pass movable types by Rvalue reference. Now it's looking like only very special cases need to do that...like these two. Things had seemed to work most of the time if you put them everywhere, but I just found one case of the compiler not running the part of the code transferring ownership.
(It was a situation like passing a unique pointer held in a variable with std::move to something taking a unique_ptr<foo> && parameter...but noticing the variable at the callsite hadn't been nulled. Changing the parameter to unique_ptr<foo> fixed it and it was properly nulled, thus preventing a double-delete. :-/ I haven't isolated why this one was bad when it seemed to work elsewhere, but a smoking gun is it worked the first time but not the subsequent calls.)
I'm sure there's a good reason for that, and many of you can saliently sum it up. In the meantime I've started going around like a good cargo-cult programmer removing the &&s.
But what if you're writing a templated class, where it looked like this?
template <class FooType>
class CantCopyMe
{
private:
CantCopyMe (CantCopyMe const & other) = delete;
CantCopyMe & operator= (CantCopyMe const & other) = delete;
public:
template<class OtherFooType>
CantCopyMe (CantCopyMe<OtherFooType> && other) {
/* ... */
}
template<class OtherFooType>
CantCopyMe & operator= (CantCopyMe<OtherFooType> && other) {
/* ... */
}
};
Is that bad practice for some reason, and you should break out separately when OtherFooType and FooType aren't the same... then it just passes by value?
template <class FooType>
class CantCopyMe
{
private:
CantCopyMe (CantCopyMe const & other) = delete;
CantCopyMe & operator= (CantCopyMe const & other) = delete;
public:
CantCopyMe (CantCopyMe && other) {
/* ... */
}
CantCopyMe & operator= (CantCopyMe && other) {
/* ... */
}
template<class OtherFooType>
CantCopyMe (CantCopyMe<OtherFooType> other) {
/* ... */
}
template<class OtherFooType>
CantCopyMe & operator= (CantCopyMe<OtherFooType> other) {
/* ... */
}
};
I think there's a simple answer for a possibly unexpected reason:
A copy-/move constructor or assignment-operator is never a template (specialization). E.g. [class.copy]/2
A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments.
Also, footnote 122 says:
Because a template assignment operator or an assignment operator taking an rvalue reference parameter is never a copy assignment operator, the presence of such an assignment operator does not suppress the implicit declaration of a copy assignment operator. Such assignment operators participate in overload resolution with other assignment operators, including copy assignment operators, and, if selected, will be used to assign an object.
Example:
#include <iostream>
#include <utility>
template<class T>
struct X
{
X() {}
template<class U>
X(X<U>&&)
{
std::cout << "template \"move\" ctor\n";
}
template<class U>
X& operator= (X<U>&&)
{
std::cout << "template \"move\" assignment-op\n";
return *this;
}
};
int main()
{
X<int> x; // no output
X<int> y(x); // no output
y = std::move(x); // no output
X<double> z( std::move(x) ); // output
y = std::move(z); // output
}
In this example, the implicitly-declared move constructor and move assignment-operator are used.
Therefore, if you don't declare a non-template move ctor and move assignment-operator, they might be declared implicitly. They're not declared implicitly e.g. for the move assignment-op, if you have a user-declared dtor; for details see [class.copy]/11 and [class.copy]/20.
Example: Adding a dtor to the example above:
#include <iostream>
#include <utility>
template<class T>
struct X
{
X() {}
~X() {}
template<class U>
X(X<U>&&)
{
std::cout << "template \"move\" ctor\n";
}
template<class U>
X& operator= (X<U>&&)
{
std::cout << "template \"move\" assignment-op\n";
return *this;
}
};
int main()
{
X<int> x; // no output
X<int> y(x); // no output
y = std::move(x); // output
X<double> z( std::move(x) ); // output
y = std::move(z); // output
}
Here, the first move-assignment y = std::move(x); calls a specialization of the assignment-operator template, because there's no implicitly declared move assignment-operator.