MSVC function matching with const enum value 0 - c++

I was bitten by an unintended C++ function match by MSVC. I can reduce it to the following test case:
#include <iostream>
enum Code { aaa, bbb };
struct MyVal {
Code c;
MyVal(Code c): c(c) { }
};
void test(int i, MyVal val) {
std::cout << "case " << i << ": value " << val.c << std::endl;
}
void test(int i, double* f) {
std::cout << "case " << i << ": WRONG" << std::endl;
}
const Code v1 = aaa;
Code v2 = aaa;
const Code v3 = bbb;
int main() {
const Code w1 = aaa;
Code w2 = aaa;
const Code w3 = bbb;
test(1, v1); // unexpected MSVC WRONG
test(2, v2);
test(3, v3);
test(4, aaa);
test(5, w1); // unexpected MSVC WRONG
test(6, w2);
test(7, w3);
return 0;
}
I expected that all 7 invocations of test would match the first overload, and GCC (live example) and Clang (live example) match this as intended:
case 1: value 0
case 2: value 0
case 3: value 1
case 4: value 0
case 5: value 0
case 6: value 0
case 7: value 1
But MSVC (live example) matches cases 1 and 5 to the "wrong" overload (I found this behavior in MSVC 2013 and 2015):
case 1: WRONG
case 2: value 0
case 3: value 1
case 4: value 0
case 5: WRONG
case 6: value 0
case 7: value 1
It seems that the conversion to a pointer is preferred by MSVC for a const enum variable with (accidental) value 0. I would have expected this behavior with a literal 0, but not with an enum variable.
My questions: Is the MSVC behavior standard-conformant? (Perhaps for an older version of C++?) If not, is this a known extension or bug?

You don't name any standards, but let's see what the differences are:
[C++11: 4.10/1]: A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion. Two null pointer values of the same type shall compare equal. 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. [..]
[C++11: 5.19/3]: A literal constant expression is a prvalue core constant expression of literal type, but not pointer type. An integral constant expression is a literal constant expression of integral or unscoped enumeration type. [..]
And:
[C++03: 4.10/1]: A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to zero. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of pointer to object or pointer to function type. Two null pointer values of the same type shall compare equal. 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).
[C++03: 5.19/2]: Other expressions are considered constant-expressions only for the purpose of non-local static object initialization (3.6.2). Such constant expressions shall evaluate to one of the following:
a null pointer value (4.10),
a null member pointer value (4.11),
an arithmetic constant expression,
an address constant expression,
a reference constant expression,
an address constant expression for a complete object type, plus or minus an integral constant expression, or
a pointer to member constant expression.
The key here is that the standard language changed between C++03 and C++11, with the latter introducing the requirement that a null pointer constant of this form be a literal.
(They always needed to actually be constants and evaluate to 0, so you can remove v2, v3, w2 and w3 from your testcase.)
A null pointer constant can convert to a double* more easily than going through your user-defined conversion, so…
I believe MSVS is implementing the C++03 rules.
Amusingly, though, if I put GCC in C++03 mode, its behaviour isn't changed, which is technically non-compliant. I suspect the change in the language stemmed from the behaviour of common implementations at the time, rather than the other way around. I can see some evidence that GCC was [allegedly] non-conforming in this regard as early as 2004, so it may also just be that the standard wording change fortuitously un-bugged what had been a GCC bug.

Related

Implementing the Linux Kernel's __is_constexpr (ICE_P) macro in pure C++

After reading about the standard C11 version of Martin Uecker's ICE_P predicate, I tried to implement it in pure C++. The C11 version, making use of _Generic selection is as follows:
#define ICE_P(x) _Generic((1? (void *) ((x)*0) : (int *) 0), int*: 1, void*: 0)
The obvious approach for C++ is to replace _Generic by a template and decltype, such as:
template<typename T> struct is_ice_helper;
template<> struct is_ice_helper<void*> { enum { value = false }; };
template<> struct is_ice_helper<int*> { enum { value = true }; };
#define ICE_P(x) (is_ice_helper<decltype(1? (void *) ((x)*0) : (int *) 0)>::value)
However, it fails the simplest test. Why can't it detect integer constant expressions?
The issue is subtle. The specification for determining the composite type of the conditional expression's pointer operands are similar in C++ to the ones in C, so it starts off looking promising:
(N4659) [expr.cond]
7 Lvalue-to-rvalue, array-to-pointer, and function-to-pointer
standard conversions are performed on the second and third operands.
After those conversions, one of the following shall hold:
[...]
One or both of the second and third operands have pointer type; pointer conversions, function pointer conversions, and qualification
conversions are performed to bring them to their composite pointer
type (Clause [expr]). The result is of the composite pointer type.
[...]
The reduction to the composite pointer type is specified as follows:
(N4659) [expr]
5 The composite pointer type of two operands p1 and p2 having
types T1 and T2, respectively, where at least one is a pointer or
pointer to member type or std​::​nullptr_­t, is:
if both p1 and p2 are null pointer constants, std​::​nullptr_­t;
if either p1 or p2 is a null pointer constant, T2 or T1, respectively;
if T1 or T2 is “pointer to cv1 void” and the other type is “pointer to cv2 T”, where T is an object type or void, “pointer to cv12 void”,
where cv12 is the union of cv1 and cv2;
[...]
So the result of our ICE_P macro is determined by which of the bullets above we land one after checking each in order. Given how we defined is_ice_helper, we know that the composite type is not nullptr_t, otherwise we'd hit the first bullet, and will get an error due to the missing template specialization. So we must be hitting bullet number 3, making the predicate report false. It all seems to hinge on the definition of a null pointer constant.
(N4659) [conv.ptr] (emphasis mine)
1 A null pointer constant is an integer literal with value
zero or a prvalue of type std​::​nullptr_­t. A null pointer
constant can be converted to a pointer type; the result is the null
pointer value of that type and is distinguishable from every other
value of object pointer or function pointer type. Such a conversion is
called a null pointer conversion. Two null pointer values of the same
type shall compare equal. 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. A null pointer constant of integral type can be converted
to a prvalue of type std​::​nullptr_­t.
Since (int*)0 is not a null pointer constant by the definition above, we do not qualify for the first bullet of [expr]/5. The composite type is not std::nullptr_t. Neither is (void *) ((x)*0) a null pointer constant, nor can it be turned into one. Removing the cast (something the definition doesn't allow) leaves us with (x)*0. This is a integer constant expression with value zero. But it is not an integer literal with value zero! The definition of a null pointer constant in C++ deviates from the one in C!
(N1570) 6.3.2.3 Pointers
3 An integer constant expression with the value 0, or such an
expression cast to type void *, is called a null pointer constant. If
a null pointer constant is converted to a pointer type, the resulting
pointer, called a null pointer, is guaranteed to compare unequal to a
pointer to any object or function.
C allows arbitrary constant expressions with value zero to form a null pointer constant, while C++ requires integer literals. Given C++'s rich support for computing constant expressions of a variety of literal types, this seems like a needless restriction. And one that makes the above approach to ICE_P a non-starter in C++.

Does the 'if' statement in C/C++ cast the operand to an integer?

When you use the if statement in C/C++, or any other logical operator, is the operand you pass to the statement cast to an integer for evaluation?
My current understanding was that the operand passed to the statement is cast to an integer to test whether or not it is non-zero (true), and that if you pass a pointer, this can be cast to an integer to evaluate with a 0/null value being defined as false.
I was under the impression that C++'s standard Bool values were simply typedef of an unsigned char with a value of 0 and 1.
Can anyone explain what's actually happening behind the scenes with this behavior?
In C++ bool is a standalone type that has nothing to do with unsigned char. And in C++ language the expression under if is indeed implicitly cast to bool type. Note: to bool specifically, not to int. For scalar types other than bool, the implicit conversion to bool is essentially defined through non-equality comparison to literal 0. I.e. values that compare non-equal are converted to true, while values that compare equal - to false.
In other words, in C++ statement
if (a)
is interpreted as
bool tmp = (bool) a;
// `tmp` is either `true` or `false`
if (tmp)
which for scalar types is interpreted as
bool tmp = a != 0;
if (tmp)
Note that this works "as expected" for pointer types as well. But instead of converting the pointer to int type, it actually works the other way around: it converts literal 0 to the proper pointer type.
For other types it could be something different, like a call to a user-defined conversion operator operator bool().
In C language the expression under if has to have scalar type. And it is implicitly compared to constant 0. Note that this does not involve converting the controlling expression it to int. Comparison to constant 0 is defined separately for all scalar types. E.g. for int it has its natural meaning, while for pointer types it is interpreted as comparison to null pointer. Now, the result of such comparison in C has type int and evaluates to either 0 or 1. That 0 or 1 is what controls what if branch is taken.
In other words, in C statement
if (a)
is immediately interpreted as
int tmp = a != 0;
// `tmp` is either `0` or `1`
if (tmp)
Additionally, your assumption that null pointer produces a zero int value when converted to int type is incorrect. Neither language makes such guarantee. Null pointer is not guaranteed to be represented by zero address value and is not guaranteed to produce zero value when converted to int.
In C++, the condition in an if statement is converted to a bool.
From the C++11 Standard:
6.4 Selection statements
4 The value of a condition that is an initialized declaration in a statement other than a switch statement is the value of the declared variable contextually converted to bool (Clause 4). If that conversion is ill-formed, the program is ill-formed.
A different section, 4.12 Boolean conversion, of the standard talks about conversions to bool.
4.12/1 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.

Initialization by null pointer constant: which behaviour is correct?

int main() {
const int x = 0;
int* y = x; // line 3
int* z = x+x; // line 4
}
Quoth the standard (C++11 §4.10/1)
A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to
zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; ...
There are four possibilities:
Line 4 is OK, but line 3 isn't. This is because x and x+x are both constant expressions that evaluate to 0, but only x+x is a prvalue. It appears that gcc takes this interpretation (live demo)
Lines 3 and 4 are both OK. Although x is an lvalue, the lvalue-to-rvalue conversion is applied, giving a prvalue constant expression equal to 0. The clang on my system (clang-3.0) accepts both lines 3 and 4.
Lines 3 and 4 are both not OK. clang-3.4 errors on both lines (live demo).
Line 3 is OK, but line 4 isn't. (Included for the sake of completeness even though no compiler I tried exhibits this behaviour.)
Who is right? Does it depend on which version of the standard we are considering?
The wording in the standard changed as a result of DR 903. The new wording is
A null pointer constant is an integer literal (2.14.2) with value zero or a prvalue of type std::nullptr_t.
Issue 903 involves a curious corner case where it is impossible to produce the "correct" overload resolution in certain cases where a template parameter is a (possibly 0) integer constant.
Apparently a number of possible resolutions were considered, but
There was a strong consensus among the CWG that only the literal 0 should be considered a null pointer constant, not any arbitrary zero-valued constant expression as is currently specified.
So, yes, it depends on whether the compiler has implemented the resolution to DR 903 or not.

Magic of casting value to bool

Today I realized that casting a value to a bool is a kind of magic:
int value = 0x100;
unsigned char uc = static_cast<unsigned char>(value);
bool b = static_cast<bool>(value);
Both sizeof(uc) and sizeof(b) return 1. I know that uc will contain 0x00, because only the LSB is copied. But b will be true, so my assumption is that, when casting to bool, the value gets evaluated instead of copied.
Is this assumption correct? And is this a standard C++ behavior?
There's nothing magical about it. The conversion from int to unsigned char is defined as value % 256 (for 8-bit chars), so that is what you get. It can be implemented as copying the LSB, but you should still think about it in the semantic sense, not the implementation.
On the same vein, conversion of int to bool is defined as value != 0, so again, that is what you get.
Integral (and boolean) conversions are covered by the C++11 standard in [conv.integral] and [conv.bool]. For the C-style cast, see [expr.cast] and [expr.static.cast].
It is a part of the standard:
4.12 Boolean conversions [conv.bool]
1 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.
Yes, when casting to bool, the value gets evaluated, not copying.
In fact, in your example, as long as value is not 0, b will be true.
Update: Quoting from C++ Primer 5th Edition Chapter 2.1.2:
When we assign one of the nonbool arithmetic types to a bool object, the
result is false if the value is 0 and true otherwise.
According to the rules for C-style casts, (bool)value is effectively a static_cast. The first rule for static_cast then kicks in, which computes the value of a temporary "declared and initialized ... as by new_type Temp(expression);", i.e. bool Temp(value);. That's well-defined: Temp is true iff value != 0. So yes, value is "evaluated" in a sense.
Casting to bool is a feature inherited from plain old C. Originally, C didn't have a bool type, and it was useful to use other types in if statements, like so:
int myBool = 1;
if(myBool) {
// C didn't have bools, so we had to use ints!
}
void* p = malloc(sizeof(int));
if(!p) {
// malloc failed to allocate memory!
}
When you're converting to bool, it acts just like you're putting the statement in an if.
Of course, C++ is backwards compatible with C, so it adopted the feature. C++ also added the ability to overload the conversion to bool on your classes. iostream does this to indicate when the stream is in an invalid state:
if(!cout) {
// Something went horribly wrong with the standard output stream!
}
ISO/IEC C++ Standard:
4.12 Boolean conversions [conv.bool] 1 A prvalue of arithmetic... type can be converted to a prvalue of type bool. A zero value... is converted to false; any other value is converted to true.
So, since a prvalue is a value, you might say that the value gets evaluated, albeit that's kind of pleonastic.

How is "int* ptr = int()" value initialization not illegal?

The following code (taken from here):
int* ptr = int();
compiles in Visual C++ and value-initializes the pointer.
How is that possible? I mean int() yields an object of type int and I can't assign an int to a pointer.
How is the code above not illegal?
int() is a constant expression with a value of 0, so it's a valid way of producing a null pointer constant. Ultimately, it's just a slightly different way of saying int *ptr = NULL;
Because int() yields 0, which is interchangeable with NULL. NULL itself is defined as 0, unlike C's NULL which is (void *) 0.
Note that this would be an error:
int* ptr = int(5);
and this will still work:
int* ptr = int(0);
0 is a special constant value and as such it can be treated as a pointer value. Constant expressions that yield 0, such as 1 - 1 are as well allowed as null-pointer constants.
The expression int() evaluates to a constant default-initialized integer, which is the value 0. That value is special: it is used to initialize a pointer to the NULL state.
From n3290 (C++03 uses similar text), 4.10 Pointer conversions [conv.ptr] paragraph 1 (the emphasis is mine):
1 A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion. [...]
int() is such an integral constant expression prvalue of integer type that evaluates to zero (that's a mouthful!), and thus can be used to initialize a pointer type. As you can see, 0 is not the only integral expression that is special cased.
Well int isn't an object.
I beleive what's happening here is you're telling the int* to point to some memory address determined by int()
so if int() creates 0, int* will point to memory address 0