Preventing implicit conversion operator only for binary operators - c++

I'm having an issue that I've boiled down to the following, where an == operator usage compiles even though it's supposed to fail (C++17, tested on GCC 5.x, 8.x, and 9.x):
template <int N> struct thing {
operator const char * () const { return nullptr; }
bool operator == (const thing<N> &) const { return false; }
};
int main () {
thing<0> a;
thing<1> b;
a == b; // i don't want this to compile, but it does
}
The reason it is compiling is because the compiler is choosing to do this:
(const char *)a == (const char *)b;
Instead of this:
// bool thing<0>::operator == (const thing<0> &) const
a.operator == (b);
Because the latter isn't a match, since b is a thing<1> not a thing<0>. (Incidentally, it also produces unused-comparison warnings when the primitive comparison operator is used; not interesting, but that's why those warnings appear.)
I've verified this (actually it's how I discovered the cause) on C++ Insights, which outputs:
template <int N> struct thing {
operator const char * () const { return nullptr; }
bool operator == (const thing<N> &) const { return false; }
};
/* First instantiated from: insights.cpp:7 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct thing<0>
{
using retType_2_7 = const char *;
inline operator retType_2_7 () const
{
return nullptr;
}
inline bool operator==(const thing<0> &) const;
// inline constexpr thing() noexcept = default;
};
#endif
/* First instantiated from: insights.cpp:8 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct thing<1>
{
using retType_2_7 = const char *;
inline operator retType_2_7 () const
{
return nullptr;
}
inline bool operator==(const thing<1> &) const;
// inline constexpr thing() noexcept = default;
};
#endif
int main()
{
thing<0> a = thing<0>();
thing<1> b = thing<1>();
static_cast<const char *>(a.operator const char *()) == static_cast<const char *>(b.operator const char *());
}
Showing the conversion operator usage in that last line of main.
My question is: Of course, the whole thing behaves properly if I make the conversion operator explicit, but I'd really like to keep the implicit conversion operator and have the compiler enforce correct usage of operator == (where "correct" = failing to compile if the parameter type is different). Is this possible?
Note that it is not important to me to have thing<N> == const char * work. That is, I don't need this overload:
bool thing<N>::operator == (const char *) const
So I don't care if a solution breaks that flavor of ==.
I did search through other posts here; there were a number with misleadingly similar titles that ended up being unrelated. It looks like this post had a related problem (undesired implicit conversions) but it's still not applicable.
For completeness, here is a slightly less minimal but more representative example of what I'm actually doing, where the intent is to allow == to work for thing<T,N> and thing<R,N>, that is, the N's must be the same but the first template parameter can differ. I'm including this in case it affects a possible solution, since this is what I really need correct behavior for:
template <typename T, int N> struct thing {
operator const char * () const { return nullptr; }
template <typename R> bool operator == (const thing<R,N> &) const { return false; }
};
int main () {
thing<int,0> i0;
thing<float,0> f0;
thing<int,1> i1;
i0 == f0;
f0 == i0;
i0 == i1; // should fail to compile
f0 == i1; // should fail to compile
i1 == i0; // should fail to compile
i1 == f0; // should fail to compile
}

You can just provide a deleted version of the operator for the case where it shouldn't work, e.g.:
template <int N> struct thing {
operator const void * () const { return nullptr; }
bool operator == (const thing<N> &) const { return false; }
template <int X>
bool operator == (const thing<X> &) const = delete;
};
int main () {
thing<0> a;
thing<1> b;
a == a; // This compiles
a == b; // Doesn't compile
}
With C++20 the logic conveniently also extends to operator!=. With pre-C++20 compiler you should probably add corresponding overloads for operator!=, too.

If you mark the operator const char * as explicit, then your code will not compile.
explicit requires C++11, but you're already using nullptr, so that's not a problem.

Related

'this' argument has type const but function is not marked const c++ overload operator

How do i fix this?
I'm getting error: 'this' argument has type const but function is not marked const c++ overload operator
template <class T>
class Rational {
private:
T n = 0;
T d = 1;
public:
Rational() = default;
T numerator() {
return n;
}
T denominator() {
return d;
}
};
template <class T>
inline bool const operator ==(const Rational <T> & lhs, const Rational <T>& rhs) {
return lhs.numerator() * rhs.denominator() == lhs.denominator() * rhs.numerator();
}
My guess is that numerator() and denominator() member functions are not const member functions. Make them const. After that, the above function should work.
BTW, there is no need for the return type to be bool const. Keep it simple and change it to bool.
If numerator() and denominator() are to be used to directly assign to Rationals internal member variables as well as being used in const contexts, you need two sets of overloads. One mutable and one const:
// mutable interface
T& Rational::numerator();
T& Rational::denominator();
// const interface if T may only be a fundamental integral type
T Rational::numerator() const;
T Rational::denominator() const;
// const interface if sizeof(T) may be > sizeof(T*)
T const& Rational::numerator() const;
T const& Rational::denominator() const;
Note, only one of the const interfaces may be used so you need to select one of them.
Here's an example of how it can be done:
#include <iostream>
#include <type_traits>
template<typename T>
class Rational {
public:
// pass by value for fundamental types, by const& for other types
using by_value_or_by_const_ref =
std::conditional_t<std::is_fundamental_v<T>, T, T const&>;
Rational(by_value_or_by_const_ref n, by_value_or_by_const_ref d) :
m_numerator(n), m_denominator(d) {}
// mutable interface
T& numerator() { return m_numerator; }
T& denominator() { return m_denominator; }
// const interface
by_value_or_by_const_ref numerator() const { return m_numerator; }
by_value_or_by_const_ref denominator() const { return m_denominator; }
private:
T m_numerator;
T m_denominator;
};
template<class T>
inline bool operator==(const Rational<T>& lhs, const Rational<T>& rhs) {
// using const interface
return lhs.numerator() * rhs.denominator() ==
lhs.denominator() * rhs.numerator();
}
int main() {
Rational<int> a(10, 20);
Rational<int> b(10, 10);
// using mutable interface
a.denominator() /= 4;
b.numerator() *= 2;
std::cout << std::boolalpha << (a == b) << "\n";
}

Why do conversion operators cause ambiguous overload when const-ref and value exist

I'm looking at a wrapping class, based on https://www.fluentcpp.com/category/strong-types/ The main difference is that I'm replacing the get() method with a explicit casting operator as this triggers questions during code review when used.
As you can see in the simplified code below, I have 3 overloads of the casting operator:
From const A & to int
From A && to int
From const A & to const int &
When writing: static_cast<int>(a), I expect the overload of const A & to int to be used. However, it seems to favor the int and the const int & overload equally. Why does it do so?
Similarly to this, it seems to allow const int &r = static_cast<const int &>(createA()); which I assume is a life-time bug. (assuming createA returns an A by value)
Simplified code at Compiler Explorer: https://gcc.godbolt.org/z/YMH9Ed
#include <utility>
struct A
{
int v = 42;
explicit operator int() const & { return v; } // Removing this line works
explicit operator int() && { return std::move(v); }
explicit operator const int &() const & { return v; }
};
int main(int, char**)
{
A a;
int r = static_cast<int>(a);
return r;
}
Compilation error:
<source>:14:13: error: ambiguous conversion for static_cast from 'A' to 'int'
int r = static_cast<int>(a);
^~~~~~~~~~~~~~~~~~~
<source>:6:14: note: candidate function
explicit operator int() const & { return v; }
^
<source>:8:14: note: candidate function
explicit operator const int &() const & { return v; }
^
explicit operator int() const & { return v; }
and
explicit operator const int &() const & { return v; }
are equally good conversions. a is a lvalue so both functions can be called. a is also not const so both functions will have to apply a const conversion to a, so they are both still equally good. All that is left is the "return type", int or const int&, but those are both equally good to create anint from.
You need to get rid of one of conversion operators, or remove the constness
from
explicit operator const int &() const & { return v; }
to turn it into
explicit operator const int &() & { return v; }
so non const lvalues give you a const reference.

C++ Trick to avoid pointer comparison

I am moving a code base from one programming style to another.
We have a type called Operand defined like:
class Operand
{...};
Then we had
class OperandFactory
{
public:
const Operand *make_operand (...);
};
OperandFactory used to hash the Operand and keep it in a table. Therefore if you called make_operand with the same arguments, you would get the same pointer and pointer comparison over Operands proliferated. Now I need to add a feature that will make this infeasible. So, I implement operator== in Operand and would like to somehow generate at compile time (better) or run time (better than nothing) error if I ever do a pointer comparison on Operands. What's the best way to achieve this?
This is only to be used during this transition stage, so I don't mind if the solution looks like a hack as long as it captures all comparisons in the code base.
you can overload the address of operator to return a handle and declare the comparison of two handles (without definition). This would lead to a linker error.
#include <iostream>
class Op;
class Handle {
Op *pri_;
public:
explicit Handle(Op *o) : pri_(o) {}
Op *operator->() const { return pri_; }
Op &operator*() const { return *pri_; }
};
// force compile time errors on comparison operators
bool operator==(const Handle &, const Handle &) = delete;
bool operator!=(const Handle &, const Handle &) = delete;
bool operator>=(const Handle &, const Handle &) = delete;
bool operator<=(const Handle &, const Handle &) = delete;
bool operator<(const Handle &, const Handle &) = delete;
bool operator>(const Handle &, const Handle &) = delete;
class Op {
int foo_;
public:
explicit Op(int i) : foo_(i) { }
Handle operator&() { return Handle(this); };
void touch() const { std::cout << "foobar"; }
};
int main(int argc, char **argv) {
Op i{10};
Op j{20};
auto c = &j; // works
c->touch(); // works
(*c).touch(); // works
if (&j == &i) {
/* will not compile */
}
}
Note:
You have to fulfill the random_access_iterator requirement for Handle!
Op i{10}
Handle ref = &i;
ref++; ref--; ++ref; --ref; ref = ref + 10; ref = ref - 10; // should all work.
Adding an operator in your Operand class won't help : you want to detect comparisons of pointers to Operands. Unfortunately, native types operators can't be overloaded, and pointers are of native type. This is not the solution you're looking for.

C++ Bool Operator==

I just tried to make a function that compares 2 objects, but it gives me:
Error: bool Duree::operator==(const Duree&, const Duree&) must take exactly one argument
How can I solve this? Thank you.
Duree.h
#ifndef DEF_DUREE
#define DEF_DUREE
class Duree
{
public:
Duree(int heures = 0, int minutes = 0, int secondes = 0);
bool estEgal(Duree const& b) const;
bool operator==(Duree const& a, Duree const& b);
private:
int m_heures;
int m_minutes;
int m_secondes;
};
#endif
Duree.cpp
#include "Duree.h"
Duree::Duree(int heures, int minutes, int secondes) : m_heures(heures), m_minutes(minutes), m_secondes(secondes)
{
}
bool Duree::estEgal(Duree const& b) const
{
return (m_heures == b.m_heures && m_minutes == b.m_minutes && m_secondes == b.m_secondes);
}
bool operator==(Duree const& a, Duree const& b)
{
return a.estEgal(b);
}
Main.cpp
#include <iostream>
#include "Duree.h"
using namespace std;
int main()
{
Duree fisrt(10, 10, 10), second(15, 20);
if (fisrt == second)
cout << "Les durees sont identiques";
else
cout << "Les durees sont differentes";
return 0;
}
Either you declare operator== as a free function with two arguments:
bool operator==(Duree const& a, Duree const& b);
or as a member function with only one argument:
bool Duree::operator==(Duree const& b);
This is because when you do x == y you are comparing only two objects. If you have a member function there's an implicit "this object" (the one you call operator== on) passed, making it 3 arguments instead of 2.
That being said, from the way you wrote the code I'm guessing you just forgot to put friend in front of the operator== declaration, in the class definition.
Probably useful tip: You can use #pragma once, on compilers that support it (basically every "main" compiler), instead of include guards. :)
Change your prototype to bool operator==(Duree const& rhs); or make it a free function out of class Duree.
Always prefer the binary functions to be NON MEMBER (free) functions. Otherwise you can run into problems if you overload the operator with different types. Consider the following contrived code:
struct Foo {
int x;
bool operator==(Foo const & other) const { return x == other.x; }
};
This should work fine for comparing two Foos together. But it has a problem.
Suppose you also want to compare to an int:
struct Foo {
int x;
bool operator==(Foo const & other) const { return x == other.x; }
bool operator==(int other) const { return x == other; }
};
Now you can compare one way but not the other:
Foo a, b;
...
a == b; // ok
a == 123; // ok
123 == a; // ERROR
As member function the object must be on the right hand side.
Simple, move the int-Foo overloads out of the class, and make two versions, Foo==int and int==Foo? (Note, making them friends declared inside the class can accomplish that too, FWIW, but I'm not showing it here.)
struct Foo {
int x;
bool operator==(Foo const & other) const { return x == other.x; }
};
bool operator==(int other, Foo const& f) { return f.x == other; }
bool operator==(Foo const& f, int other) { return f.x == other; }
So now everything works, right? We have a mix of member and non-member operators.
a == b; // ok
a == 123; // ok
123 == a; // ok
Until Foo starts getting member functions that want to use the operators...
struct Foo {
int x;
bool operator==(Foo const & other) const { return x == other.x; }
void g(int x);
};
bool operator==(int other, Foo const& f) { return f.x == other; }
bool operator==(Foo const& f, int other) { return f.x == other; }
void Foo::g(int x)
{
Foo f = getOtherFoo();
if (f == x) { // ERROR! cannot find operator==(Foo,int)
//...
}
}
It STILL has a problem! Inside members of of Foo, it cannot see the non-member operators because the member one hides them! g() will not compile since it can't compare Foo to int. Moving all of the operators out will resolve it, since then all of the overloads are in the same scope.
(Remember, name-lookup keeps searching outer scopes UNTIL it finds the fist case of the name it is looking for, and then only considers all the names it finds in that scope. Inside g(), it's scope is in the class, and since it finds one version of the operator inside the class (the wrong one), it never looks outside the class for more overloads. But if they are all outside the class, it'll find them all at the same time.)
struct Foo {
int x;
void g(int x);
};
// NON MEMBER
bool operator==(Foo const & lhs, Foo const & rhs) { return lhsx == rhs.x; }
bool operator==(int other, Foo const& f) { return f.x == other; }
bool operator==(Foo const& f, int other) { return f.x == other; }
void Foo::g(int x)
{
Foo f = getOtherFoo();
if (f == x) { // OK now, finds proper overload
//...
}
}
Now, it compiles in all the cases. That's why they say, "Always prefer to make non-member binary operator overloads." Otherwise you can end up with symmetry and hiding problems.
I'm on the same kind of issue.
Worked by putting operator== with two arguments back in main.cpp before the main() ;)

Unintended behavior from boost::operators

I'm taking boost::operators (clang 2.1, boost 1.48.0) for a spin, and ran into the following behavior I can't explain. It seems that when I add my own operator double() const method to my class Ex (as I'd like to allow my users to idiomatically use static_cast<double>() on instances of my class), I no longer get a compiler error when trying to use operator== between dissimilar classes. In fact, it seems that operator== is not called at all.
Without operator double() const, the class works completely as expected (save for that it now lacks a conversion operator), and I receive the correct complier error when trying f == h.
So what is the right way to add this conversion operator? Code below.
// clang++ -std=c++0x boost-operators-example.cpp -Wall -o ex
#include <boost/operators.hpp>
#include <iostream>
template <typename T, int N>
class Ex : boost::operators<Ex<T,N>> {
public:
Ex(T data) : data_(data) {};
Ex& operator=(const Ex& rhs) {
data_ = rhs.data_;
return *this;
};
T get() {
return data_ * N;
};
// the troubling operator double()
operator double() const {
return double(data_) / N;
};
bool operator<(const Ex& rhs) const {
return data_ < rhs.data_;
};
bool operator==(const Ex& rhs) const {
return data_ == rhs.data_;
};
private:
T data_;
};
int main(int argc, char **argv) {
Ex<int,4> f(1);
Ex<int,4> g(2);
Ex<int,2> h(1);
// this will fail for obvious reasons when operator double() is not defined
//
// error: cannot convert 'Ex<int, 4>' to 'double' without a conversion operator
std::cout << static_cast<double>(f) << '\n';
std::cout
// ok
<< (f == g)
// this is the error I'm supposed to get, but does not occur when I have
// operator double() defined
//
// error: invalid operands to binary expression
// ('Ex<int, 4>' and 'Ex<int, 2>')
// note: candidate function not viable: no known conversion from
// 'Ex<int, 2>' to 'const Ex<int, 4>' for 1st argument
// bool operator==(const Ex& rhs) const
<< (f == h)
<< '\n';
}
You should mark your operator double() as explicit. That allows the static cast, but prevents it being used as an implicit conversion when you test for equality (and in other cases).