18

Question: Does implicit bool conversions always fall back to attempting implicit conversion to void*? (If such a conversion function exists for the type). If so, why?

Consider the following short program:

#include <iostream>

class Foo{
public:

    operator void*() const
    {
        std::cout << "operator void*() const" << std::endl;
        return 0;
    }
};

int main()
{
    Foo f;

    if(f)
        std::cout << "True" << std::endl;
    else
        std::cout << "False" << std::endl;

    return 0;
}

The output of this program is:

operator void*() const
False

meaning, the conversion function to void* was called. If we tag an explicit qualifier in front of the conversion function then the implicit conversion to void* would fail.

Edit: It seems many answers are that "null pointers can be converted to false". I understand this, my question was regarding the "if I can't directly call operator bool() then I will try conversion to any pointer".

jensa
  • 2,792
  • 2
  • 21
  • 36
  • "will try conversion to any pointer" - `void` pointer isn't "any" pointer. `void` pointer is `void` pointer, nothing else. It just accepts addresses of any types. – xinaiz Jul 22 '16 at 21:32
  • 1
    @BlackMoses I meant any pointer type, which indeed seems to be the case given the accepted answer. – jensa Jul 22 '16 at 21:33
  • @JesperJuhl It's not a stupid question. It's asking about if the compiler is allowed to do this, and the reasoning behind it. It may be fairly obvious to you, but for beginner to intermediate developers, this could be confusing. – PC Luddite Jul 23 '16 at 06:34

5 Answers5

16

If the compiler cannot convert a user-defined type to bool directly, then it tries to do it indirectly, i.e. convert (via an user-defined conversion) to a type that can be converted to bool without involving another user-defined conversion. The list of such types includes (and seems to be limited to) the following types:

  • an integer arithmetic type (char, int, etc)
  • a floating point arithmetic type (float, double, long double)
  • a pointer type (void* belongs here, but it could as well be const std::vector<Something>*)
  • a pointer to function (including a pointer to a member function)
  • a reference type to any of the above

Note however that only one such indirect conversion must exist. If two or more conversions from the above list are possible, then the compiler will face an ambiguity and will report an error.

Leon
  • 31,443
  • 4
  • 72
  • 97
  • [This example](https://godbolt.org/z/3Yv61q) looks contradictory and it works on both clang and gcc. From the standard I'd expect having both two conversion operators to trigger an error. Maybe there's somewhere on the standard stating a preference for numeric (non-pointer) types. – Euller Borges Oct 19 '20 at 19:27
6

For a few references in the Standard:

  • §6.4.0.4 [stmt.select]

    The value of a condition that is an expression is the value of the expression, contextually converted to bool for statements other than switch

  • §4.0.4 [conv]

    Certain language constructs require that an expression be converted to a Boolean value. An expression e appearing in such a context is said to be contextually converted to bool and is well-formed if and only if the declaration bool t(e); is well-formed, for some invented temporary variable t.

  • §8.5.17 [dcl.init]

    The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression.

  • §8.5.17.7 [dcl.init]

    Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated (13.3.1.5), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.

  • §13.3.1.5 [over.match.conv]

    Assuming that “cv1 T is the type of the object being initialized, and “cv S is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:

    The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T via a standard conversion sequence (13.3.3.1.1) are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T with a qualification conversion (4.4) are also candidate functions.

  • §4.13.1 [conv.bool]

    A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true.

peppe
  • 21,934
  • 4
  • 55
  • 70
4

What's really happening is that your class has an implicit conversion to a pointer type void* in this case. You return 0, which is the NULL macro, which is accepted as a pointer type.

Pointers have implicit conversion to booleans, and null pointers convert to false.

Really you could have a different implicit conversion to pointer for Foo:

operator int*() const
{
    std::cout << "operator int* const" << std::endl;
    return new int(3);
}

And your output will change to

operator int* const
True

However, if you have both, then you get a compiler error:

class Foo{
public:

    operator int*() const
    {
        std::cout << "operator int* const" << std::endl;
        return new int(3);
    }
    operator void*() const
    {
        std::cout << "operator void*() const" << std::endl;
        return 0;
    }
};

main.cpp:26:9: error: conversion from 'Foo' to 'bool' is ambiguous

If, however, you explicitly define a conversion, too bool, then it is not ambiguous:

operator void*() const
{
    std::cout << "operator void*() const" << std::endl;
    return 0;
}

operator bool() const
{
     std::cout << "operator bool() const" << std::endl;
    return true;
} // <--- compiler chooses this one

The topic of implicit conversions actually quite interesting, because it mirrors how the compiler chooses an appropriate member function for given arguments (value conversions, integral promotions, etc.).

That is, the compiler has a prioritized list it will choose from when trying to determine what you mean. If two overloads have the same priority, you get an error.

For example, the operator bool will always be chosen, but if you instead had to choose from operator int and operator void*, then operator int would be chosen because it chooses numeric conversion over pointer conversions.

However, if you had both operator int and operator char, then you'd get an error because they are both numeric integral conversions.

AndyG
  • 39,700
  • 8
  • 109
  • 143
  • Thank you, a good answer. But it does not really answer my question as to why C++ will "fall back" and attempt to convert my object to any pointer type? – jensa Jul 22 '16 at 21:28
  • @jensa: See my edits for a more thorough explanation. – AndyG Jul 22 '16 at 21:36
2

Any integral conversion operator would work the same way. You return 0 in your operator, hence the False.

operator [int,double,uint64_t,<any_integral>]() const
{
    std::cout << "integral operator called" << std::endl;
    return 0;
}

Any integral type can be used in logical expression.

xinaiz
  • 7,744
  • 6
  • 34
  • 78
0

This may be any type that can be used in a boolean context, and void * is not special here, eh?

bipll
  • 11,747
  • 1
  • 18
  • 32