Why Is There an Implicit Type Conversion from Pointers to Bool in C++

Why is there an implicit type conversion from pointers to bool in C++?

It's very common in C to write this

void f(T* ptr) {
if (ptr) {
// ptr is not NULL
}
}

You should make a const char* constructor.

C++, does bool conversion always fall back to implicit conversion to void*?

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.

What is the best way to disable implicit conversion from pointer types to bool when constructing an std::variant?

I think the cleanest way to handle implicit conversion doing surprising things with respect to variants is to use a "strong variant" type if the behavior of std::variant is a problem; i.e., implement a variant type that enforces construction only using types that are exactly the types in the variant.

It is easy to test if a type is a member of a parameter pack in C++17 using std::disjunction, leading to an implementation as below:

template<typename... Ts>
class strong_variant : public std::variant<Ts...>
{
public:
template <typename T, typename U =
typename std::enable_if<std::disjunction_v<std::is_same<T, Ts>...>>::type>
strong_variant(T v) : std::variant<Ts...>(v)
{}

strong_variant() : std::variant<Ts...>()
{}
};

struct foo {};
struct bar {};

int main()
{
foo f;
bar b;
const foo c_f;

strong_variant<foo*, std::string, bool> sv;

sv = &f; // okay.
sv = true; // okay.
sv = "foo"s; // okay.

sv = "foo"; //no, must a string.
sv = &b; // no, must be a foo.
sv = &c_f; // no, must be non-const.
}

Is failure to implicitly convert a pointer to _Bool a compiler deficiency?

Yes, this is a compiler bug. While C has no implicit conversions in either direction between integer types and pointer types, nor implicit conversions between pointer types except in special cases like to/from pointer-to-void or from pointer-to-unqualified to pointer-to-qualified, it does define an implicit conversion to _Bool. (Traditionally many compilers supported such implicit conversions in other places, but doing so was harmful and was not part of the C language.)

The language about implicit conversion is under 6.5.16.1 Simple assignment:

One of the following shall hold:

  • the left operand has atomic, qualified, or unqualified arithmetic type, and the right has arithmetic type;
  • the left operand has an atomic, qualified, or unqualified version of a structure or union type compatible with the type of the right;
  • the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
  • the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) one operand is a pointer to an object type, and the other is a pointer to a qualified or unqualified version of void, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
  • the left operand is an atomic, qualified, or unqualified pointer, and the right is a null pointer constant; or
  • the left operand has type atomic, qualified, or unqualified _Bool, and the right is a pointer.

In other places in the standard where implicit conversion appears, it's specified "as if by assignment", referring to the above. For example, in 6.5.2.2 Function calls:

If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, ...

Note that your question is actually about initialization, which is not assignment in C but something different. However, 6.7.9 Initialization ¶11 covers it:

The initializer for a scalar shall be a single expression, optionally enclosed in braces. The initial value of the object is that of the expression (after conversion); the same type constraints and conversions as for simple assignment apply, taking the type of the scalar to be the unqualified version of its declared type.

C++ rely on implicit conversion to bool in conditions?

In the strictest sense, you can rely on implicit conversions to bool. Backwards compatibility with C demands it.

Thus it becomes a question of code readability. Often the purpose of code standards is to enforce a sameness to the code style, whether you agree with the style or not. If you're looking at someone else's standard and wondering if you should incorporate it into your own, go ahead and debate it - but if it's your company's long-standing rule, learn to live with it.

P.S. I just joined an organization that has the same rule. I'm actually fine with it because I've always believed that explicit is better than implicit. The one thing I can't abide though is bool condition; ... if (condition == true), which so redundant that it grates on my eyes.

Any decent compiler should generate the same code whether the check is implicit or explicit, so that shouldn't be a consideration.

Implicit cast of null pointer to bool

See this standard reference (bold emphasis mine):

C++11 §4.12 Boolean conversions

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.
A prvalue of type
std::nullptr_t can be converted to a prvalue of type bool; the
resulting value is false.

The middle sentence is relevant: it is telling you that the null pointer value (foo = nullptr) can be implicitly cast to false which itself has type bool. Therefore if (!foo) is well-defined.

Why const char* implicitly converted to bool rather than std::string?

Because the implicit conversion from const char* to bool is qualified as standard conversion, while const char* to std::string is user-defined conversion. The former has higher ranking and wins in overload resolution.

A standard conversion sequence is always better than a user-defined conversion sequence or an ellipsis conversion sequence.

BTW: mystruct obj(c); performs direct initialization, explicit converting constructors including mystruct::mystruct(bool) are considered too. As the result, c is converted to bool then passed to mystruct::mystruct(bool) as argument to construct obj.

Direct-initialization is more permissive than copy-initialization: copy-initialization only considers non-explicit constructors and non-explicit user-defined conversion functions, while direct-initialization considers all constructors and all user-defined conversion functions.

About explicit specifier,

  1. Specifies that a constructor or conversion function (since C++11) or deduction guide (since C++17) is explicit, that is, it cannot be used for implicit conversions and copy-initialization.

Why is a const pointer accidentally converted to bool?

We can't stop the implicit conversion (bool conversion) from pointer to bool; You can add another overloaded constructor taking const A*, which will be selected in overload resolution when passing const A*, because it's an exact match and B::B(bool) requires an implicit conversion. With marking it as delete, the program becomes ill-formed if it's selected.

struct B
{
explicit B(bool b)
{
std::cout << "B(bool)" << std::endl;
}
explicit B(A*)
{
std::cout << "B(A*)" << std::endl;
}
B(const A*) = delete;
};

LIVE

Or you can mark the overloaded constructor taking pointer types delete, then all the pointer type can't be passed to B::B, except A* for which the constructor is declared seperately like you've done.

template <typename T>
B(T*) = delete;

LIVE

Why does pointer to int convert to void* but pointer to function convert to bool?

Based on the above, it is perfectly OK to convert a function pointer or a pointer to an int to a void* as well as bool.

The quotation states that a pointer to an object can be converted to cv void *. Functions are not objects, and this disqualifies the conversion to cv void *, leaving only bool.


However, given the choice of both, which one should a pointer convert to?

It should convert to const void * over bool. Why? Well, prepare for a journey that starts in Overload Resolution (§13.3 [over.match]/2). Emphasis mine, of course.

But, once the candidate functions and argument lists have been identified, the selection of the best function is the same in all cases:

— First, a subset of the candidate functions (those that have the proper number of arguments and meet
certain other conditions) is selected to form a set of viable functions (13.3.2).

— Then the best viable function is selected based on the implicit conversion sequences (13.3.3.1) needed to match each argument to the corresponding parameter of each viable function.

So what about these implicit conversion sequences?

Let's jump over to §13.3.3.1 [over.best.ics]/3 and see just what an implicit conversion sequence is:

A well-formed implicit conversion sequence is one of the following forms:

— a standard conversion sequence (13.3.3.1.1),

— a user-defined conversion sequence (13.3.3.1.2), or

— an ellipsis conversion sequence (13.3.3.1.3).

We're interested in standard conversions sequences. Let's pop over to Standard Conversion Sequences (§13.3.3.1.1 [over.ics.scs]):

1 Table 12 summarizes the conversions defined in Clause 4 and partitions them into four disjoint categories: Lvalue Transformation, Qualification Adjustment, Promotion, and Conversion. [ Note: These categories are orthogonal with respect to value category, cv-qualification, and data representation: the Lvalue Transformations do not change the cv-qualification or data representation of the type; the Qualification Adjustments do not change the value category or data representation of the type; and the Promotions and Conversions do not change the value category or cv-qualification of the type. — end note ]

2 [ Note: As described in Clause 4, a standard conversion sequence is either the Identity conversion by itself (that is, no conversion) or consists of one to three conversions from the other four categories.

The important part is in /2. A standard conversion sequence is allowed to be a single standard conversion. These standard conversions are listed in Table 12, shown below. Notice that both your Pointer Conversions and Boolean Conversions are in there.

Table of standard conversions and their categories and ranks

From here, we learn something important: Pointer conversions and boolean conversions have the same rank. Remember that as we head to Ranking Implicit Conversion Sequences (§13.3.3.2 [over.ics.rank]).

Looking at /4, we see:

Standard conversion sequences are ordered by their ranks: an Exact Match is a better conversion than a Promotion, which is a better conversion than a Conversion. Two conversion sequences with the same rank are indistinguishable unless one of the following rules applies:

— A conversion that does not convert a pointer, a pointer to member, or std::nullptr_t to bool is
better than one that does.

We've found our answer in the form of a very explicit statement. Hooray!

Is converting a bool (false) to a pointer legal in C++?

This should not be accepted since C++11.

See Pointer conversions (emphasis mine):

A null pointer constant (see NULL), can be converted to any pointer type, and the result is the null pointer value of that type. Such conversion (known as null pointer conversion) is allowed to convert to a cv-qualified type as a single conversion, that is, it's not considered a combination of numeric and qualifying conversions.

Note since C++11 a null pointer constant might be an integer literal with value zero (or a prvalue of type std::nullptr_t), while false is not, it's a boolean literal.

And until C++11 null pointer constant is defined as an integral constant expression rvalue of integer type that evaluates to zero, while false is fine. (GCC will give a warning for it.)

From the standard, $4.10/1 Pointer conversions [conv.ptr] (emphasis mine)

A null pointer constant is an integer literal (2.13.2) with value zero or a prvalue of type std::nullptr_t.

The conversion of a null pointer constant to a pointer to cv-qualified
type is a single conversion, and not the sequence of a pointer
conversion followed by a qualification conversion (4.4)
.



Related Topics



Leave a reply



Submit