2

Consider the following class:

class foo
{
public:
    constexpr operator bool() const noexcept;
    constexpr operator void * &() noexcept;
    constexpr operator void * const &() const noexcept;
    constexpr operator void const * &() noexcept;
    constexpr operator void const * const &() const noexcept;
};

A foo object would be used like this:

void bar(bool);
// ...
foo f;
bar(f); // error C2664: cannot convert argument 1 from 'foo' to 'bool'
        // message : Ambiguous user-defined-conversion
        // message : see declaration of 'bar'

The issue is that the operator bool is not considered because of its constness. If I make another function without the const qualifier, the problem is solved. If I make f const, the problem is solved as well. If I explicitly cast f to bool, the problem is solved.

Sample to work with

What are my other options and what is causing this ambiguity?

  • make conversion operators `explicit` – user7860670 Jul 09 '20 at 07:23
  • @user7860670 I'd like them to be implicit. –  Jul 09 '20 at 07:27
  • You wrote "I don't even understand why this is happening in the first place." - it is happening because c++ for whatever reason allows implicit conversions. They drastically complicate overload resolution and adding more implicit user-defined conversion operators only makes things worse. – user7860670 Jul 09 '20 at 07:32
  • It seems like you already have enough implicit conversions for one more `constexpr operator bool() noexcept;` not to matter. I do agree with @user7860670 that its better to have none in the first place. – Benny K Jul 09 '20 at 07:38
  • @user7860670 Blaming "C++ is overcomplicated" is not a satisfactory answer. –  Jul 09 '20 at 07:39
  • @BennyK Why isn't it resolving the already-made bool operator that is const? Sure, I can do it, but why is this happening in the first place? –  Jul 09 '20 at 07:40
  • 3
    Overloads #2 and #4 are better fits for the function `bar` because they aren't constant. According to this: https://en.cppreference.com/w/cpp/language/implicit_conversion, the "qualification adjustment" (i.e. const to non-const) is last in line. – Benny K Jul 09 '20 at 07:48

2 Answers2

1

First you should have a look at this question regarding conversion precedence in C++.

Then as pointed by some comments, operator bool() was not selected by the compiler for this conversion and the ambiguous resolution message is not about it.

The ambiguity comes from constexpr operator void * &() noexcept versus constexpr operator void const * &() noexcept. The idea being: let's try to convert a non const object to something and then see if this something can be converted to bool.

Also operator void const*() and operator void*() are redundant because there is no situation when you can call one and not the other and you can get a const void* from a void*.

Also returning void const * const & does not have any advantage over returning void const* as you won't be able to modify the reference. Returning a pointer instead of a reference to a pointer at worst does not change anything, at best, prevent you from doing a double indirection.

My advice would be to remove the non const operators and replace them by an explicit setter when you want to change the underlying pointer stored in foo. But that might not be the solution you are looking for depending on your actual problem and your design choices.

Guillaume Gris
  • 2,135
  • 17
  • 34
  • "the compiler does not even consider `operator bool()` here" - This is incorrect. If the other functions are removed, `operator bool()` is selected. – Benny K Jul 09 '20 at 09:21
0

Take a look at the following, simplified, code:

struct foo {
    constexpr operator bool() const noexcept; // (1)
    constexpr operator void * &() noexcept; // (2)
    constexpr operator void const * &() noexcept; // (3)
};

void bar(bool);

// ...

foo f;
bar(f);

The implicit conversion rules state the following:

Order of the conversions

Implicit conversion sequence consists of the following, in this order:

  1. zero or one standard conversion sequence;
  2. zero or one user-defined conversion;
  3. zero or one standard conversion sequence.

The conversion of the argument in bar is one user-defined conversion, followed by one standard conversion sequence.

Continued:

... When converting from one built-in type to another built-in type, only one standard conversion sequence is allowed. A standard conversion sequence consists of the following, in this order:

  1. zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion, and function-to-pointer conversion;
  2. zero or one numeric promotion or numeric conversion;
  3. zero or one function pointer conversion; (since C++17)
  4. zero or one qualification adjustment.

There are planty of types that can be used where a bool is required, and pointers are amongst them, so for overloads #2 and #3 all that is required is one "lvalue-to-rvalue conversion". To use overload #1 the compiler will have to perform a "qualification adjustment" (const bool to bool).


How to fix this:

Remove ambiguity by adding the constexpr operator bool() noexcept;, which fits bar exactly.

Benny K
  • 868
  • 6
  • 18