Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MSVC function matching with const enum value 0

Tags:

c++

visual-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?

like image 477
Bruno De Fraine Avatar asked Dec 09 '16 10:12

Bruno De Fraine


1 Answers

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.

like image 133
Lightness Races in Orbit Avatar answered Sep 30 '22 15:09

Lightness Races in Orbit