25

This problem came up when answering this question about overload resolution with enums.

While the case for long long was definitely a bug in MSVC2012NovCTP (according to the standard text and a test with gcc 4.7.1), I cannot figure out why the following behavior occurs:

#include <iostream>

enum charEnum : char { A = 'A' };

void fct(char)      { std::cout << "fct(char)"      << std::endl; }
void fct(int)       { std::cout << "fct(int)"       << std::endl; }
void fct(long long) { std::cout << "fct(long long)" << std::endl; }

int main() 
{
    fct('A');
    fct(A);
}

Both MSVC2012NovCTP and gcc 4.7.1 agree on this output:

fct(char)
fct(int)

Shouldn't A be converted from charEnum to char? Why is A being converted to int?

EDIT: clang complains that the call is ambiguous, which agrees with my interpretation below; that said, I would still find it much more intuitive if it were only considered to be the underlying type.


Two relevant standard excerpts are §7.2/9:

The value of an enumerator or an object of an unscoped enumeration type is converted to an integer by integral promotion (4.5)

And §4.5/4:

A prvalue of an unscoped enumeration type whose underlying type is fixed (7.2) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type.

So charEnum can either be converted to char, or any integral promotion of char, such as int.

But this is vague to me because "can" doesn't quite say which will actually be chosen. If anything, this should be ambiguous with this wording because no preference is given between char or any of its promotions. If you comment out fct(int), then the call is ambiguous. Why is int special?

The only thing I can think of is that integral promotions are applied recursively, but nothing I see mandates it.

Community
  • 1
  • 1
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • Just guessing: To keep old code working, because before C++11, an enum casts to `int`. Now with the type specifier (`char` in your case, I don't know how it is called correctly), we just want to say what type should be used to represent it, not how it is treated in conversions. – leemes Jan 08 '13 at 00:26
  • @leemes: Eh, kind of. An "old-style" enum casts to its underlying type, which is either `int`, `unsigned int`, `long`, etc. until one correctly holds all the enumerator values. So this one would cast to `unsigned int`: `enum foo { X = 0, Y = UINT_MAX };`. Regardless though your point still stands: it was never "less" than `int`. But since the entire concept of a fixed underlying type is new, it's strange the break the intuitiveness for no compatability reasons. – GManNickG Jan 08 '13 at 00:30
  • Sounds like a defect in the Standard's description of integral promotions to me. Every other type mentioned is described as being able to promote to zero or one exactly specified promoted type. So when other parts of the Standard say "integral promotion", everyone knows exactly what that does. But here it sounds like there are two possible promoted types?? – aschepler Jan 08 '13 at 00:32
  • Have a look at this: http://ideone.com/8zuGEp - EDIT: Well, it doesn't show anything new, I think. Can we reproduce something pro/contra my argument? – leemes Jan 08 '13 at 00:34
  • Oh, wait... Maybe the "best" conversion is the one with size max(underlying type, default type)? (With default type, I mean the type the enum would have if we don't specify the type). At least, this would explain why in my ideone code, `fct(A)` uses `uint`, not `int`, since it can't... – leemes Jan 08 '13 at 00:38
  • Another problem that comes up: where 5.2.2p7 [expr.call] discusses passing an argument which matches an ellipsis, "the value of the argument is converted to the promoted type before the call". – aschepler Jan 08 '13 at 00:44
  • I can't find where it's defined, but experimentally, it appears as though an integral promotion to `int` is considered a better match than an integral promotion to any other integral type during function resolution. This seems to be what's happening here, since the enumerator value can be promoted to either the underlying enumeration type (`char`), or any integral value that this type can be promoted to (e.g. `int`). – Lily Ballard Jan 08 '13 at 00:57
  • 7.2/9 says that it is promoted, 4.5/4 says that integral promotion can be applied to the underlying type (7.2/9 changes the can to an is). 4.5/1 says that stuff smaller than itn can be converted to int (again 7.2/9 changes can to is). – Nathan Binkert Jan 08 '13 at 04:12
  • @NathanBinkert: Why should integral promotions be applied twice? – GManNickG Jan 08 '13 at 04:28
  • @GManNickG It seems to me that the second sentence of 4.5/4 says to do so. – Nathan Binkert Jan 08 '13 at 16:22
  • @GManNickG: Somebody posted the same question on [isocpp.org](https://groups.google.com/a/isocpp.org/forum/?fromgroups=#!topic/std-discussion/jvtNzjy39Ng). – Jesse Good Jan 09 '13 at 20:44
  • @JesseGood: Thanks for the info! I'll wait to accept an answer once one of the existing answers (which has quite the variety!) is "chosen" to be right more officially. – GManNickG Jan 09 '13 at 20:50

2 Answers2

9

In C++03, the rule was:

An rvalue of an unscoped enumeration type (7.2 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e. the values in the range bmin to bmax as described in 7.2 [dcl.enum]): int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int.

In a C++03 compiler, int would be chosen because it is the first on the list.


In C++11, the underlying type was introduced. Accordingly, via 685. Integral promotion of enumeration ignores fixed underlying type , this wording was changed to the paragraph you quoted in §4.5/4 and from reading the defect report, it seems the intention of the committee was for fct(char) (the underlying type) to be chosen.

However, according to the discussion under core issue 1601, the text in C++11 actually makes the conversion ambiguous (fct(char) and fct(int) are both possible and neither is preferred).

The following fix was proposed and accepted into C++14:

A conversion that promotes an enumeration whose underlying type is fixed to its underlying type is better than one that promotes to the promoted underlying type, if the two are different.

Since it was reported as a defect in C++11, compilers should apply this fix when in C++11 mode and call fct(char).

M.M
  • 138,810
  • 21
  • 208
  • 365
Jesse Good
  • 50,901
  • 14
  • 124
  • 166
  • Hm, sounds reasonable, but I just tested clang 3.3 and it complains saying all three overloads are ambiguous. I really think it would be silly for this case to be ambiguous but clang's is closest to what I was thinking, while your answer is closest to what people would be hoping. Is clang wrong or do all the potential promotions apply at once? – GManNickG Jan 08 '13 at 06:38
  • According to the overload resolution rules, the call is ambiguous, and as you mention, you interpret `§4.5/4` as meaning there is no preference. So based on that, clang is correct. However, according to what I read in the defect report and how I interpret the intention of `§4.5/4`, I believe the `fct(char)` should be selected. – Jesse Good Jan 08 '13 at 07:31
  • Clang 4.1 (i.e. Apple's 3.1) prefers `fct(int)`, and only if that definition is removed does it declare the rest to be ambiguous. So it appears that Clang has recently changed to use the behavior GManNickG describes. Which also matches the standard as per my reading too (e.g. promotion to any integral type has the same rank, so it should be ambiguous). It sounds like Jesse's deficit report indicates that this was an error and that `fct(chat)` should be selected. – Lily Ballard Jan 08 '13 at 08:01
  • @KevinBallard Why should `int` be special? – Potatoswatter Jan 08 '13 at 08:29
  • @Potatoswatter: Presumably because in C++03 an unscoped enum could be implicitly converted to `int`, and from there it could be subsequently converted to other integral types, so resolving function overload in favor of `int` is preferred as it is the shorter sequence of conversions (e.g. enum => int, instead of enum => int => other integral). But C++11 has changed that, such that all conversions enum => integral have the same rank, and Clang 3.3 apparently respects that now. – Lily Ballard Jan 08 '13 at 08:45
  • Ah, my reading of previous comment was that "Clang 4.1 (i.e. Apple's 3.1) … has recently changed … Which also matches … my reading" but you meant 3.3 matches your reading, which is also what I describe. – Potatoswatter Jan 08 '13 at 08:52
4

According to my interpretation of the current standard, the call has to be ambiguous. Follows an explanation.

Per 4.5/4:

"A prvalue of an unscoped enumeration type whose underlying type is fixed (7.2) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type."

This offers two alternative promotions: a promotion to the underlying type, and a promotion to the promoted underlying type. Hence, this paragraph alone introduces ambiguity as for which of these alternatives should be used when resolving a function call to overloaded functions.

Then, paragraph 13.3.3 decides which is the best viable function of an overload set in terms of "conversion sequence". In particular, relevant for this matter is 13.3.3.1 ("Implicit conversion sequences") and, more specifically, 13.3.3.1.1 ("Standard conversion sequences"), which defines what elementary steps these conversion sequences are made of.

13.3.3.1.1/1 and Table 12 classify these steps into four categories, among which Promotion and Conversion, and rank conversion sequences based on the category of individual conversions that make up those sequences.

In our case, we have two one-length conversion sequences made up of a single Promotion step (both permitted by 4.5./4).

Conversion sequences are ranked according to 13.3.3.2. In particular, 13.3.3.2/3 mentions that a conversion sequence S1 is preferable to a conversion sequence S2 if:

"the rank [i.e. Promotion, Conversion, etc.] of S1 is better than the rank of S2, or S1 and S2 have the same rank and are distinguishable by the rules in the paragraph below, or, if not that, [...]"

The "paragraph below" being mentioned is 13.3.3.2/4, which says:

"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:"

Then, what follows is a set of rules that do not apply in our situation.

Therefore, we have two one-step conversion sequences made up of a single Promotion with the same rank. According to the above interpretation of the current standard, the call has to be ambiguous.

Personally, however, I agree that a change is needed to make the conversion to the fixed underlying type of an unscoped enumeration preferable to other possible conversions.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Your mixing up integral promotion and integral conversion. An unscoped enumeration type to integral type is always a `Promotion` **not** a `Conversion`. Although they both do conversions (notice that conversion with small 'c' can refer to both). – Jesse Good Jan 08 '13 at 20:55
  • @JesseGood: so the conversion to `int` is a `Promotion` while the conversion to `char` (the underlying type) is a `Conversion`? If this is the case, then 13.3.3.2/4 explains why the overload accepting `int` is preferred to the one accepting `char` – Andy Prowl Jan 08 '13 at 21:04
  • No, they are both `Promotion`s. So you are correct that they have the same rank. Also, clang 3.3 complains that all three calls are ambiguous [as mentioned here](http://stackoverflow.com/questions/14206403/why-does-a-value-of-an-enum-with-a-fixed-underlying-type-of-char-resolve-to-fct/14208457#comment19704618_14208457) which backs up that. Also, in C++03 the rules were different which explains why `int` was being selected. – Jesse Good Jan 08 '13 at 21:14
  • @JesseGood: you're right, I've been pretty dumb actually: paragraph 4.5 is titled "Integral *Promotions*"... I will edit my answer. Thank you – Andy Prowl Jan 08 '13 at 21:20