Assignment operator overloading with a C++ class template - c++

I have a C++ class template for representing real- and complex valued 2D fields. I'd like to overload the assignment operator to achieve deep-copying the data from one field to another. For now, I've restricted the data to either double or std::complex<double>. This means there are 4 different cases to consider: double-to-double, double-to-std::complex<double>, std::complex<double>-to-double and std::complex<double>-to-std::complex<double>. I want to handle the std::complex<double>-to-double case by taking the real part of the complex value; for the other cases it's just a trivial assignment. I'm however struggling to get the code to compile. The following is a simple mock version that captures the issues:
#include <complex>
template<class T>
class Number {
private:
// should make Number<T>.m_value visible to Number<U>
template<class U>
friend class Number;
T m_value;
public:
Number(const T value) : m_value{value} {
// restricting the data
static_assert(
std::is_same<T, double>::value || std::is_same<T, std::complex<double>>::value,
"Error: Number::Number: Only 'double' and 'std::complex<double>' are supported currently!"
);
// no copying allowed
Number(const Number& orig) = delete;
};
// general case
template<class U>
Number<T>& operator=(const Number<U>& another) {
m_value = another.m_value;
return *this;
}
};
// specialization
template<> Number<double>& Number<double>::operator=(const Number<std::complex<double>>& another) {
m_value = std::real(another.m_value);
}
int main(int argc, char** argv) {
const std::complex<double> I{0.0, 1.0};
Number<double> n{0.0};
Number<std::complex<double>> m{1.0 + I*2.0};
n = m;
return 0;
}
Compiler output:
g++ -c -g -std=c++14 -MMD -MP -MF "build/Debug/GNU-Linux/main.o.d" -o build/Debug/GNU-Linux/main.o main.cpp
main.cpp:28:28: error: template-id ‘operator=<>’ for ‘Number<double>& Number<double>::operator=(const Number<std::complex<double> >&)’ does not match any template declaration
template<> Number<double>& Number<double>::operator=(const Number<std::complex<double>>& another) {
^
main.cpp:28:97: note: saw 1 ‘template<>’, need 2 for specializing a member function template
template<> Number<double>& Number<double>::operator=(const Number<std::complex<double>>& another) {
^
main.cpp: In instantiation of ‘Number<T>& Number<T>::operator=(const Number<U>&) [with U = std::complex<double>; T = double]’:
main.cpp:36:5: required from here
main.cpp:23:15: error: cannot convert ‘const std::complex<double>’ to ‘double’ in assignment
m_value = another.m_value;
^
I can't seem to figure out how to implement the assignment overloads. I have tried to find a solution for my problem (e.g. from "Similar questions") and have come across many helpful questions and answers, and have incorporated many things from them to my code. I however haven't found a solution to my specific problem and seem to be stuck. Any suggestions? Thanks!

It actually says what it expects in the line:
main.cpp:28:97: note: saw 1 ‘template<>’, need 2 for specializing a member function template
You need one 'template<>' to specialize T in class template and one more to specialize U in member function template:
template<>
template<>
Number<double>& Number<double>::operator=(const Number<std::complex<double>>& another) { ... }

Related

C++ function header matching: how does matching work when const and templates are both involved?

I had a templated function that I wished to call. This is (a trimmed-down version of) the header:
template <typename Item>
void print (shared_ptr<const MyContainer<Item>> stuff, ostream& out)
which I tried to call with a line like this:
print (make_shared<MyContainer<int>>(42), cerr);
But the compiler complained that there was no match. What confuses me is that the const mismatch is not a problem, because if I redeclare my function to omit the template it works:
void print (shared_ptr<const MyContainer<int>> stuff, ostream& out) //matches!
On the other hand, if I omit constness, the templated version does work:
template <typename Item>
void print (shared_ptr<MyContainer<Item>> stuff, ostream& out) //also matches!
But I should be able to write a function over const things and pass it a non-const value (which the function will then just not modify), right? Indeed, if I go back to non-managed pointers, the corresponding old way to write the header would have been
template <typename Item>
void print (const MyContainer<Item>* stuff, ostream& out)
and indeed then a call to
print (new MyContainer<int>(42), cerr); //yet another match!
once again just fine.
So, what is it about this particular cocktail of shared_ptr, templates, and const that causes the compiler to be unable to find the matching function? (Running g++ 8.2.1, and clang++ 7.0.1 seems to produce the same result.)
Concerning const-ness of pointee, std::shared_ptr behaves a bit different than raw-pointers.
A std::shared_ptr<T> is not the same as a std::shared_ptr<const T>. It's even not that compatible to allow an implicit conversion. (The error message in Daniels answer says this quite literally.)
It doesn't work for the same reason like in the following (counter) example:
template <typename T>
struct ContainerT {
T a;
ContainerT(T a): a(a) { }
ContainerT(const ContainerT&) = default;
ContainerT& operator=(const ContainerT&) = default;
};
int main()
{
ContainerT<int> a(42);
ContainerT<const int> b(a);
return 0;
}
Output:
g++ (GCC) 8.2.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
main.cpp: In function 'int main()':
main.cpp:15:28: error: no matching function for call to 'ContainerT<const int>::ContainerT(ContainerT<int>&)'
ContainerT<const int> b(a);
^
main.cpp:8:3: note: candidate: 'constexpr ContainerT<T>::ContainerT(const ContainerT<T>&) [with T = const int]'
ContainerT(const ContainerT&) = default;
^~~~~~~~~~
main.cpp:8:3: note: no known conversion for argument 1 from 'ContainerT<int>' to 'const ContainerT<const int>&'
main.cpp:7:3: note: candidate: 'ContainerT<T>::ContainerT(T) [with T = const int]'
ContainerT(T a): a(a) { }
^~~~~~~~~~
main.cpp:7:3: note: no known conversion for argument 1 from 'ContainerT<int>' to 'int'
Live Demo on coliru
In the case of std::shared_ptr, there is a way to circumvent this issue
→ a std::const_pointer_cast can be used:
#include <iostream>
#include <memory>
template <typename T>
struct ContainerT {
T a;
ContainerT(T a): a(a) { }
};
template <typename T>
void print(std::shared_ptr<const ContainerT<T>> ref, std::ostream &out)
{
out << "print: '" << ref->a << "'\n";
}
int main()
{
print(std::make_shared<const ContainerT<int>>(42), std::cout);
print(std::const_pointer_cast<const ContainerT<int>>(std::make_shared<ContainerT<int>>(42)), std::cout);
return 0;
}
Output:
print: '42'
print: '42'
Live Demo on coliru
For convenience, the const-cast might be done in another function template:
#include <iostream>
#include <memory>
template <typename T>
struct ContainerT {
T a;
ContainerT(T a): a(a) { }
};
template <typename T>
void print(std::shared_ptr<const ContainerT<T>> ref, std::ostream &out)
{
out << "print const: '" << ref->a << "'\n";
}
template <typename T>
void print(std::shared_ptr<ContainerT<T>> ref, std::ostream &out)
{
out << "print non-const: ";
print(std::const_pointer_cast<const ContainerT<T>>(ref), out);
}
int main()
{
print(std::make_shared<const ContainerT<int>>(42), std::cout);
print(std::make_shared<ContainerT<int>>(42), std::cout);
return 0;
}
Output:
print const: '42'
print non-const: print const: '42'
Live Demo on coliru
Here is a simplified code:
template <typename T>
void f(std::shared_ptr<const std::vector<T>>) { }
void g(std::shared_ptr<const std::vector<int>>) { }
int main() {
f(std::make_shared<std::vector<int>>()); // ERROR
g(std::make_shared<std::vector<int>>()); // OK
}
To understand what happens, read the error message, e.g., the one printed by g++:
...
note: template argument deduction/substitution failed:
note: types 'const std::vector<T>' and 'std::vector<int>' have incompatible cv-qualifiers
It tells you that the problem is with template argument deduction/substitution. The C++ rules seemingly do not allow this type of deduction. (If I have some time, I will try to find a relevant part of the Standard).
However, you can skip the template argument deduction by providing an explicit template argument:
f<int>(std::make_shared<std::vector<int>>()); // OK

operator== does not compile if I include <iostream>

The following code compiles perfectly if:
I don't include <iostream> or
I name operator== as alp::operator==.
I suppose there is a problem with <iostream> and operator==, but I don't know what.
I compile the code with gcc 7.3.0, clang++-6.0 and goldbolt. Always the same error.
The problem is that the compiler is trying to cast the parameters of operator== to const_iterator, but why? (I suppose the compiler doesn't see my version of operator==, and looks for other versions).
#include <vector>
#include <iostream> // comment and compile
namespace alp{
template <typename It_base>
struct Iterator {
using const_iterator = Iterator<typename It_base::const_iterator>;
operator const_iterator() { return const_iterator{}; }
};
template <typename It_base>
bool operator==(const Iterator<It_base>& x, const Iterator<It_base>& y)
{ return true;}
}// namespace
struct Func{
int& operator()(int& p) const {return p;}
};
template <typename It, typename View>
struct View_iterator_base{
using return_type = decltype(View{}(*It{}));
using const_iterator =
View_iterator_base<std::vector<int>::const_iterator, Func>;
};
using view_it =
alp::Iterator<View_iterator_base<std::vector<int>::iterator, Func>>;
int main()
{
view_it p{};
view_it z{};
bool x = operator==(z, p); // only compiles if you remove <iostream>
bool y = alp::operator==(z,p); // always compile
}
Error message:
yy.cpp: In instantiation of ‘struct View_iterator_base<__gnu_cxx::__normal_iterator<const int*, std::vector<int> >, Func>’:
yy.cpp:9:73: required from ‘struct alp::Iterator<View_iterator_base<__gnu_cxx::__normal_iterator<const int*, std::vector<int> >, Func> >’
yy.cpp:44:29: required from here
yy.cpp:28:42: error: no match for call to ‘(Func) (const int&)’
using return_type = decltype(View{}(*It{}));
~~~~~~^~~~~~~
yy.cpp:22:10: note: candidate: int& Func::operator()(int&) const <near match>
int& operator()(int& p) const {return p;}
^~~~~~~~
yy.cpp:22:10: note: conversion of argument 1 would be ill-formed:
yy.cpp:28:42: error: binding reference of type ‘int&’ to ‘const int’ discards qualifiers
using return_type = decltype(View{}(*It{}));
~~~~~~^~~~~~~
I've made a more minimal test case here: https://godbolt.org/z/QQonMG .
The relevant details are:
A using type alias does not instantiate a template. So for example:
template<bool b>
struct fail_if_true {
static_assert(!b, "template parameter must be false");
};
using fail_if_used = fail_if_true<true>;
will not cause a compile time error (if fail_if_used isn't used)
ADL also inspects template parameter classes. In this case, std::vector<int>::iterator is __gnu_cxx::__normal_iterator<const int*, std::vector<int> >, Func>, which has a std::vector<int> in it's template. So, operator== will check in the global namespace (always), alp (As alp::Iterator is in alp), __gnu_cxx and std.
Your View_iterator_base::const_iterator is invalid. View_iterator_base::const_interator::result_type is defined as decltype(Func{}(*std::vector<int>::const_iterator{})). std::vector<int>::const_iterator{} will be a vectors const iterator, so *std::vector<int>::const_iterator{} is a const int&. Func::operator() takes an int&, so this means that the expression is invalid. But it won't cause a compile time error if not used, for the reasons stated above. This means that your conversion operator is to an invalid type.
Since you don't define it as explicit, the conversion operator (To an invalid type) will be used to try and match it to the function parameters if they don't already match. Obviously this will finally instantiate the invalid type, so it will throw a compile time error.
My guess is that iostream includes string, which defines std::operator== for strings.
Here's an example without the std namespace: https://godbolt.org/z/-wlAmv
// Avoid including headers for testing without std::
template<class T> struct is_const { static constexpr const bool value = false; } template<class T> struct is_const<const T> { static constexpr const bool value = true; }
namespace with_another_equals {
struct T {};
bool operator==(const T&, const T&) {
return true;
}
}
namespace ns {
template<class T>
struct wrapper {
using invalid_wrapper = wrapper<typename T::invalid>;
operator invalid_wrapper() {}
};
template<class T>
bool operator==(const wrapper<T>&, const wrapper<T>&) {
return true;
}
}
template<class T>
struct with_invalid {
static_assert(!is_const<T>::value, "Invalid if const");
using invalid = with_invalid<const T>;
};
template<class T>
void test() {
using wrapped = ns::wrapper<with_invalid<T>>;
wrapped a;
wrapped b;
bool x = operator==(a, b);
bool y = ns::operator==(a, b);
}
template void test<int*>();
// Will compile if this line is commented out
template void test<with_another_equals::T>();
Note that just declaring operator const_iterator() should instantiate the type. But it doesn't because it is within templates. My guess is that it is optimised out (where it does compile because it's unused) before it can be checked to show that it can't compile (It doesn't even warn with -Wall -pedantic that it doesn't have a return statement in my example).

Why don't the any_cast function overloads cause ambiguity?

Boost's <boost/any.hpp> has:
template<typename ValueType>
ValueType any_cast(any & operand);
template<typename ValueType>
inline ValueType any_cast(const any & operand);
(among other variants.) Shouldn't this combination cause ambiguity in calls such as boost::any_cast<int>(my_any); ?
I'm asking because if I write this program:
#include <boost/any.hpp>
#include <iostream>
template<typename ValueType>
ValueType any_cast(boost::any & operand)
{
return boost::any_cast<ValueType>(operand);
}
int main()
{
int x = 123;
boost::any my_any(x);
std::cout << "my_any = " << any_cast<int>(my_any) << "\n";
return 0;
}
I do get a complaint about ambiguity:
g++ -std=c++14 -O3 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp: In function 'int main()':
main.cpp:14:57: error: call of overloaded 'any_cast(boost::any&)' is ambiguous
std::cout << "my_any = " << any_cast<int>(my_any) << "\n";
^
main.cpp:5:11: note: candidate: ValueType any_cast(boost::any&) [with ValueType = int]
ValueType any_cast(boost::any & operand)
^~~~~~~~
In file included from main.cpp:1:0:
/usr/local/include/boost/any.hpp:281:22: note: candidate: ValueType boost::any_cast(const boost::any&) [with ValueType = int]
inline ValueType any_cast(const any & operand)
^~~~~~~~
/usr/local/include/boost/any.hpp:258:15: note: candidate: ValueType boost::any_cast(boost::any&) [with ValueType = int]
ValueType any_cast(any & operand)
^~~~~~~~
Why would the calls be ambiguous? The way you call the function the any argument is an lvalue. Thus, the any argument will either be const-qualified in which case the second overload is the only potential match or it is not const-qualified in which case the first overload is the better match (there is no conversion needed while the second overload would need a conversion from any& to any const&). If you call the function with a temporary any, it could bind to an rvalue overload (i.e., taking any&&) or, if that doesn't exist, it can bind to the const-qualified overload but not the non-const-qualified overload, again, not causing any ambiguity.
Actually, there is something interesting happening here: without the overload in the global namespace the function using the explicit template argument cannot be used! However, as soon as any function template is present, even a non-matching one, it can be used! Here is an example:
namespace foo {
struct bar {};
template <typename T> void bar_cast(bar&) {}
template <typename T> void bar_cast(bar const&) {}
template <typename T> void bar_cast(bar&&) {}
}
struct whatever;
template <typename T> void bar_cast(whatever);
int main()
{
foo::bar b;
bar_cast<int>(b);
}

operator= Overload from a templated class

I have a project will all my classes templated for int, double and float, getCoordinate return an object of the type CCoordinate.
tempCoordinate = m_shapes.at(i)->getCoordinate(j);
Before I apply the templates it was working correctly. But then some errors appear.
From what I understand I need I'm missing and operator= overload to typecast the values in case for example that i have a float and I'm receiving an int, for example:
CCoordinate<float> coorFloat;
CCoordinate<int> coorInt = coorFloat
How can i create this on my class? what format does it need ? .
I was thinking that it should look like this, but apparently i'm mistaken.
//CCoordinate.h
template<class T>
class CCoordinate {
//Code
public:
template<class U> template <class U> CCoordinate<T>
operator= (const CCoordinate<U>& c1);
}
//CCoordinate.cpp
template <class U >
CCoordinate<U> CCoordinate<T>::operator= (const CCoordinate<U>& c1)
{
// some kind of casting ?
}
My Errors:
19:06:43 **** Incremental Build of configuration Debug for project ShapesRefV2 ****
Info: Internal Builder is used for build
g++ -O0 -g3 -Wall -c -fmessage-length=0 -Werror=return-type -o "myCode\\CRectangle.o" "..\\myCode\\CRectangle.cpp"
g++ -O0 -g3 -Wall -c -fmessage-length=0 -Werror=return-type -o "myCode\\CPlane.o" "..\\myCode\\CPlane.cpp"
..\myCode\CPlane.cpp: In instantiation of 'GraSys::CRectangle<T> GraSys::CPlane<T>::boundingBox(std::string, std::string) [with T = int; std::string = std::basic_string<char>]':
..\myCode\CPlane.cpp:165:24: required from here
..\myCode\CPlane.cpp:115:20: error: no match for 'operator=' (operand types are 'GraSys::CCoordinate<double>' and 'const GraSys::CCoordinate<int>')
tempCoordinate = m_shapes.at(i)->getCoordinate(j);
^
..\myCode\CPlane.cpp:115:20: note: candidate is:
In file included from ..\myCode\CGraphicElement.h:14:0,
from ..\myCode\CPlane.h:11,
from ..\myCode\CPlane.cpp:9:
..\myCode\CCoordinate.h:17:7: note: GraSys::CCoordinate<double>& GraSys::CCoordinate<double>::operator=(const GraSys::CCoordinate<double>&)
class CCoordinate
^
..\myCode\CCoordinate.h:17:7: note: no known conversion for argument 1 from 'const GraSys::CCoordinate<int>' to 'const GraSys::CCoordinate<double>&'
..\myCode\CPlane.cpp: In instantiation of 'GraSys::CRectangle<T> GraSys::CPlane<T>::boundingBox(std::string, std::string) [with T = float; std::string = std::basic_string<char>]':
..\myCode\CPlane.cpp:166:24: required from here
..\myCode\CPlane.cpp:115:20: error: no match for 'operator=' (operand types are 'GraSys::CCoordinate<double>' and 'const GraSys::CCoordinate<float>')
tempCoordinate = m_shapes.at(i)->getCoordinate(j);
^
..\myCode\CPlane.cpp:115:20: note: candidate is:
In file included from ..\myCode\CGraphicElement.h:14:0,
from ..\myCode\CPlane.h:11,
from ..\myCode\CPlane.cpp:9:
..\myCode\CCoordinate.h:17:7: note: GraSys::CCoordinate<double>& GraSys::CCoordinate<double>::operator=(const GraSys::CCoordinate<double>&)
class CCoordinate
^
..\myCode\CCoordinate.h:17:7: note: no known conversion for argument 1 from 'const GraSys::CCoordinate<float>' to 'const GraSys::CCoordinate<double>&'
19:06:44 Build Finished (took 674ms)
In the member declaration you have template <class U> too many times, and the member should return a reference to *this, so it needs to return CCordinate & (the <T> is implied if you omit it):
// Remove this vvvvvvvvvvvvvvvvvv
template<class U> /* template <class U> */
CCoordinate & operator= (const CCoordinate<U>& c1);
// ^- Return type changed to be a reference.
Since the member is a template and the class is a template, you have two levels of templates. You need to specify both levels when you implement the member.
It is also returning the wrong type (it returns CCoordinate<U> but you have declared it to return CCoordinate<T> in the class).
// You need the T template as well.
// vvvvvvvvvvvvvvv
template <class T>
template <class U>
CCoordinate<T> & CCoordinate<T>::operator= (const CCoordinate<U>& c1)
// ^ ^- Added reference as per above.
// \---- Changed to T; U makes no sense here and conflicts with your member
// declaration in the class.
{
// Your logic to make the conversion.
return *this;
}
There are two issues with your attempt. The simpler issue is that the declaration syntax of your operator= has an extra template <class U>. It should look like this:
template<class U> CCoordinate<T>
operator= (const CCoordinate<U>& c1);
However, even a correctly defined operator= will not allow you to write
CCoordinate<float> coorFloat;
CCoordinate<int> coorInt = coorFloat;
This is because the second line above copy initializes coorInt. operator= is not considered for copy initialization - it only looks at user-defined conversions which only include non-explicit constructors and non-explicit conversion functions in this case.

enable_if for generic operator T()

Here's a sample program:
#include <type_traits>
#include <stdio.h>
template <typename X>
struct test
{
operator int() const { puts("?"); return 0; }
template <typename T, typename = typename std::enable_if<std::is_same<X, void*>::value, T>::type>
operator T() const { puts("T"); return 0; }
};
int main()
{
test<void*> t;
char* c = (char*)t;
switch (t)
{
case 0: break;
}
return 0;
}
And this is the error that g++-4.7 gives
user#user:~$ g++-4.7 -std=c++0x test.cpp
test.cpp: In function ‘int main()’:
test.cpp:13:14: error: ambiguous default typeconversion from ‘test<void*>’
test.cpp:13:14: error: candidate conversions include ‘template<class T, class> test::operator void*() const [with T = T; <template-parameter-2-2> = <template-parameter-1-2>; X = void*]’
g++ 4.6 compiles it without errors and different operators are actually called.
Is there a way to make this work under g++ 4.7?
UPDATE: actually it works in 4.6 without any enable_if at all... so the question still applies but I'm now not sure if enable_if will help.
If you add an explicit cast to int here:
switch ((int)t)
Then it should compile.
I think it's complaining about the conversion being ambiguous since there exists more than one type that can hold a 0 value.
I'm using g++ 4.8 though.
I found at least one "acceptable" solution:
#define switch(x) \
switch( (typename switch_type<__typeof(x)>::type)(x) )
which switch_type trait can be extended to resolve ambiguity for specific app-related types (property types).