Overload resolution for template operators in namespaces - c++

I ran into a bit of trouble with template operator overloads when entering namespaces. Consider addition of arrays:
// overloads.hpp
#include <array>
namespace mylib {
template <size_t N>
using DoubleArray = std::array<double,N>;
template <size_t N>
DoubleArray<N> operator+( const DoubleArray<N>& lhs, const DoubleArray<N>& rhs ) {return DoubleArray<N>();}
}
Testing this in namespace mylib works as intended.
// test.cpp
#include "overloads.hpp"
namespace mylib {
void test()
{
DoubleArray<3> a({1.0,0.0,0.0});
DoubleArray<3> b({0.0,1.0,0.0});
DoubleArray<3> c(a+b); // <-- ok
}
}
Now suppose that I have a Complex class in namespace mylib::mysublib that has its own operator+ and a constructor from DoubleArray (this constructor has to be explicit to prevent implicit conversion):
// nested.cpp
#include "overloads.hpp"
namespace mylib {
namespace mysublib {
struct Complex
{
Complex() {};
explicit Complex( const DoubleArray<2>& components );
DoubleArray<2> _components;
};
Complex operator+(const Complex& rhs, const Complex& lhs) {return Complex();}
void testNested()
{
DoubleArray<2> a({1.0,0.0});
DoubleArray<2> b({0.0,1.0});
DoubleArray<2> c(a+b); // <-- no match for ‘operator+’
DoubleArray<2> d( mylib::operator+(a,b) ); // <-- ok
}
}
}
Error message:
error: no match for ‘operator+’ (operand types are ‘mylib::DoubleArray<2> {aka std::array<double, 2>}’ and ‘mylib::DoubleArray<2> {aka std::array<double, 2>}’)
DoubleArray<2> c(a+b); // <-- no match for ‘operator+’
Why can't the overloaded operator be found when called from the nested namespace? The whole point of overloading (in this example) would be a clean syntax. Any ideas on how to get this working, or if it's even possible?

The operator+ of Complex can be declared as friend function in Complex, which does not pollute the global namespace. Your example should compile after the following change.
struct Complex {
Complex(){};
explicit Complex(const DoubleArray<2>& components);
DoubleArray<2> _components;
friend Complex operator+(const Complex& rhs, const Complex& lhs) { return Complex(); }
};
According to C++ standard working draft N4140,
When two or more different declarations are specified for a single name in the same scope, that name is said to be overloaded.
In your case, the two operator+ functions are declared in different namespace and thus are not qualified for overload resolution.
When compiler finds the first match Complex operator+(const Complex& rhs, const Complex& lhs), DoubleArray cannot be implicitly converted to Complex. Therefore, you got the no match for ‘operator+’ error.

Replace your third code by
namespace mylib {
namespace mysublib {
struct Complex
{
Complex() {};
explicit Complex( const DoubleArray<2>& components );
DoubleArray<2> _components;
};
//Complex operator+(const Complex& rhs, const Complex& lhs) {return Complex();}
void testNested()
{
DoubleArray<2> a({1.0,0.0});
DoubleArray<2> b({0.0,1.0});
DoubleArray<2> c(a+b); // <-- no match for ‘operator+’
DoubleArray<2> d( mylib::operator+(a,b) ); // <-- ok
}
}
}
, then It compiles.
The definition Complex operator+(const Complex& rhs, const Complex& lhs) {return Complex();} hides the intented operator

Related

operator+= between custom complex type and std::complex

I want my custom complex type to be able to interact with std::complex, but in certain cases, the compiler do not convert my type to std::complex.
Here is a minimal working example:
#include <complex>
#include <iostream>
template <typename Expr>
class CpxScalarExpression
{
public:
inline std::complex< double > eval() const { return static_cast<Expr const&>(*this).eval(); }
inline operator std::complex< double >() const { return static_cast<Expr const&>(*this).eval(); }
};
class CpxScalar : public CpxScalarExpression<CpxScalar>
{
public:
CpxScalar() : m_value(0) {}
CpxScalar(const double value) : m_value(value) {}
CpxScalar(const double real_value, const double imag_value) : m_value(real_value, imag_value) {}
CpxScalar(const std::complex< double > value) : m_value(value) {}
template<typename Expr>
CpxScalar(const CpxScalarExpression< Expr >& expr) : m_value(expr.eval()) {}
public:
inline std::complex< double > eval() const { return m_value; }
private:
std::complex< double > m_value;
};
int main()
{
CpxScalar a(10,-5);
//std::complex< double >* b = reinterpret_cast< std::complex< double >* >(&a);
std::complex< double > b = a;
b += a;
//std::cout << b->real() << " " << b->imag();
std::cout << b.real() << " " << b.imag();
}
The compiler fails at deducing which operator+= to call and returns the following error
est.cpp:50:4: error: no match for ‘operator+=’ (operand types are ‘std::complex<double>’ and ‘CpxScalar’)
50 | b += a;
| ~~^~~~
In file included from test.cpp:1:
/usr/include/c++/9/complex:1287:7: note: candidate: ‘std::complex<double>& std::complex<double>::operator+=(double)’
1287 | operator+=(double __d)
| ^~~~~~~~
/usr/include/c++/9/complex:1287:25: note: no known conversion for argument 1 from ‘CpxScalar’ to ‘double’
1287 | operator+=(double __d)
| ~~~~~~~^~~
/usr/include/c++/9/complex:1329:9: note: candidate: ‘template<class _Tp> std::complex<double>& std::complex<double>::operator+=(const std::complex<_Tp>&)’
1329 | operator+=(const complex<_Tp>& __z)
| ^~~~~~~~
/usr/include/c++/9/complex:1329:9: note: template argument deduction/substitution failed:
test.cpp:50:7: note: ‘CpxScalar’ is not derived from ‘const std::complex<_Tp>’
50 | b += a;
| ^
Is there a way to overcome this issue ?
Providing your own overload for the operator is the way to go.
However, there's a few things to keep in mind:
Since you already have a cast available, all you have to do is to use it and let the regular operator take it from there.
Since the type that is meant to "pose" as std::complex is CpxScalarExpression<Expr>, then that should be the one the overload operates on.
std::complex's operator+=() normally allows you to add together complex values of different types, so we should maintain that. Meaning the operator should be templated on the incoming std::complex's components type.
We need to make sure to return exactly whatever std::complex's operator+= wants to return. Using decltype(auto) as the return type of the overload provides you with just that.
Putting all of that together, we land at:
template<typename T, typename Expr>
constexpr decltype(auto) operator+=(
std::complex<T>& lhs,
const CpxScalarExpression<Expr>& rhs) {
return lhs += std::complex<double>(rhs);
}
Dropping that into the code you posted makes it work just as expected, and should give you feature parity with std::complex's operator+=().
A custom operator for += works with your example:
[[maybe_unused]] constexpr static inline std::complex<double> operator+=(
const std::complex<double>& lhs,
const CpxScalar& rhs) noexcept {
return lhs + rhs.eval();
}

deleted friend declaration declares a non-template function

I want to delete a specific overloaded friend function in a class.
The below code compiles, and works in the way intended.
However, I get the following warning (g++, c++11,14,17):
edit: Warning is present using gcc9.3 and older, but not for gcc10
warning: friend declaration 'bool operator==(const MyType<BaseT>&, const BaseT&)' declares a non-template function [-Wnon-template-friend]
6 | friend bool operator==(const MyType<BaseT> &lhs, const BaseT &rhs) = delete;
| ^~~~~~
new.cpp:6:72: note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)
Can someone shed light on the warning? Why does it apply for the deleted function, but not the implemented one?
template <typename BaseT> struct MyType {
BaseT v;
explicit MyType(BaseT tv) : v(tv) {}
explicit operator BaseT() const { return v; }
friend bool operator==(const MyType<BaseT> &lhs, const BaseT &rhs) = delete;
friend bool operator==(const MyType<BaseT> &lhs, const BaseT &&rhs) {
return lhs.v == rhs;
}
};
int main(){
MyType<int> a{3};
int b{3};
// a==b; //Fails to compile: good. Do not allow comparisons between different types
a==3; //Compiles fine: good. Allow literal comparison, without implicit constructor
}

template parameter is ambiguous / deduced conflicting types for parameter

The program below generates a compiler error:
MSVC: error C2782: 'double dot(const V &,const V &)': template parameter 'V' is ambiguous
GCC: deduced conflicting types for parameter 'const V' ('Matrix<3, 1>' and 'UnitVector')
I had thought that it would not have this problem because the constructor UnitVector(Vector) is marked explicit, and therefore the arguments (of the call to dot()) can only resolve as Vector with implicit conversions. Can you tell me what I am misunderstanding? Does the compiler consider explicit constructors as implicit conversions when resolving template parameters?
template<int M, int N>
struct Matrix {
};
using Vector = Matrix<3,1>;
struct UnitVector : Vector{
UnitVector(){}
explicit UnitVector(const Vector& v)
{}
operator const Vector&(){
return *static_cast<const Vector*>(this);
}
};
template<typename V>
double dot(const V& a, const V& b){
return 0.0;
}
int main()
{
dot(Vector(),UnitVector());
}
No, that does not work. But actually, you don't need the template
double dot(const Vector &, const Vector &) {...}
works. You don't even need the conversion operator defined in UnitVector. Child to base conversions are done implicitly.
If you want to generally take two types that are implicitly convertible to a common type, the following should work (untested)
template<class U>
double dot_impl(const U&, const U&) {...}
template<class U, class V>
auto dot(const U &u, const V &v) {
return dot_impl<std::common_type_t<U, V>>(u, v);
}
Since the template parameter is explicit, the implicit conversions to thw common type of those two are done in the call, so everything works nice. I moved the original dot to dot_impl, since otherwise we would call dot with one template parameter, which could still be ambiguous.

How to use template function for implicit conversion

Very simplified example (nevermind what the class A and operators are doing, it's just for example):
#include <iostream>
using namespace std;
template <bool is_signed>
class A {
public:
// implicit conversion from int
A(int a) : a_{is_signed ? -a : a}
{}
int a_;
};
bool operator==(A<true> lhs, A<true> rhs) {
return lhs.a_ == rhs.a_;
}
bool operator==(A<false> lhs, A<false> rhs) {
return lhs.a_ == rhs.a_;
}
int main() {
A<true> a1{123};
A<false> a2{123};
cout << (a1 == 123) << endl;
cout << (a2 == 123) << endl;
return 0;
}
This works.
But if I replace two operator=='s (with same body) with template:
template <bool is_signed>
bool operator==(A<is_signed> lhs, A<is_signed> rhs) {
return lhs.a_ == rhs.a_;
}
, its compilation produces errors:
prog.cpp: In function ‘int main()’:
prog.cpp:31:14: error: no match for ‘operator==’ (operand types are ‘A<true>’ and ‘int’)
cout << (a1 == 123) << endl;
~~~^~~~~~
prog.cpp:23:6: note: candidate: ‘template<bool is_signed> bool operator==(A<is_signed>, A<is_signed>)’
bool operator==(A<is_signed> lhs, A<is_signed> rhs) {
^~~~~~~~
prog.cpp:23:6: note: template argument deduction/substitution failed:
prog.cpp:31:17: note: mismatched types ‘A<is_signed>’ and ‘int’
cout << (a1 == 123) << endl;
^~~
Is it possible to use template here? Can I use C++17 user-defined template deduction guides somehow? Or anything else?
Implicit conversions are not considered in template argument deduction, which causes the deduction for is_signed fails on the 2nd function argument.
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.
If you always use the operator== in the style like a1 == 123, i.e. an A<is_signed> is always used as the 1st operand, you can exclude the 2nd function parameter from the deduction. e.g.
template <bool is_signed>
bool operator==(A<is_signed> lhs, std::type_identity_t<A<is_signed>> rhs) {
return lhs.a_ == rhs.a_;
}
LIVE
PS: std::type_identity is supported since C++20; even it's not hard to implement one.
Another alternative is friend function, so the function is not template, but use the template argument:
template <bool is_signed>
class A {
public:
// implicit conversion from int
A(int a) : a_{is_signed ? -a : a}
{}
int a_;
friend bool operator==(const A& lhs, const A& rhs) {
return lhs.a_ == rhs.a_;
}
};
Demo
Template argument deduction does not take implicit conversions into account. You need again two overloaded comparison operators.
template <bool is_signed>
bool operator==(A<is_signed> lhs, A<is_signed> rhs) {
return lhs.a_ == rhs.a_;
}
template <bool is_signed>
bool operator==(A<is_signed> lhs, int rhs) {
return lhs == A<is_signed>(rhs);
}

std::tuple member by member comparison fails

I wanted to test this very interesting answer and came out with this minimal implementation:
class A
{
enum M { a };
std::tuple<int> members;
public:
A() { std::get<M::a>(members) = 0; }
A(int value) { std::get<M::a>(members) = value; }
A(const A & other) { members = other.members; }
int get() const { return std::get<M::a>(members); }
bool operator==(A & other) { return members == other.members; }
};
and a simple test:
int main() {
A x(42);
A y(x);
std::cout << (x==y) << std::endl;
return 0;
}
Everything's fine, until I define a simple struct B {}; and try to add an instance of it as a member. As soon as I write
std::tuple<int, B> members;
the operator== isn't ok anymore and I get this message from the compiler (gcc 5.4.1):
error: no match for ‘operator==’ (operand types are ‘std::__tuple_element_t<1ul, std::tuple<int, B> > {aka const B}’ and ‘std::__tuple_element_t<1ul, std::tuple<int, B> > {aka const B}’)
return bool(std::get<__i>(__t) == std::get<__i>(__u))
^
I tried providing an operator== to B:
struct B
{
bool operator==(const B &){ return true; }
};
and had an extra from compiler:
candidate: bool B::operator==(const B&) <near match>
bool operator==(const B &){ return true; }
^
Can anyone explain what's wrong with B struct? Is it lacking something, or else?
It's ultimately const correctness. You didn't const qualify B's (or A's, for that matter) comparison operator and its parameter consistently.
Since tuple's operator== accepts by a const reference, it cannot use your const-incorrect implementation. And overload resolution fails as a consequence.
Tidying up all of those const qualifiers resolves all the errors.