Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preventing implicit conversion operator only for binary operators

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
}
like image 550
Jason C Avatar asked Mar 02 '23 19:03

Jason C


1 Answers

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.

like image 144
Dietmar Kühl Avatar answered Apr 05 '23 22:04

Dietmar Kühl