Consider the following code:
enum ABC : char
{
a, b, c
};
void ff(char c)
{
cout << "char\n";
}
void ff(int i)
{
cout << "int\n";
}
int main()
{
ff(a); // char
}
May I ask why complier matches ff(char)
instead of ff(int)
?
I came up with this question when I was reading C Primer (5th Edition). On page 835, the writers state:
... we can pass an object or enumerator of an unscoped enumeration to a parameter of integral type. When we do so, the
enum
value promotes toint
or to a larger integral type ... Regardless of its underlying type, objects and the enumerators ... are promoted toint
.
My understanding to the above quote is that, when being passed to a function expecting an integral argument, an enumerator will first be casted "at least" into an int
. So, I'm expecting the above code to call ff(int)
. Actually, even my Visual Studio editor is showing that: (Sorry I know that we should avoid having screenshot here but I just want to show what I saw)
I also noticed that if I don't explicitly specify the underlying type for ABC
, then ff(int)
will be called.
So my current guess goes like this: If we don't explicitly specify the underlying type, then the object/enumerator passed to the integral parameter will first be casted into an int
. But if the underlying type is explicitly specified, then the compiler will first try to match the overloaded function which expects the specified type.
May I ask if my guess is correct?
CodePudding user response:
May I ask why complier matches ff(char) instead of ff(int)?
Because an enum with fixed underlying type can implicitly convert to its underlying type. The underlying type is char
and thus the conversion to char
is the most preferred candidate overload.
So my current guess goes like this: If we don't explicitly specify the underlying type, then the object/enumerator passed to the integral parameter will first be casted into an int.
A cast is an explicit conversion. The conversion in this example is implicit.
The exact preferred conversion depends on the values of the enum. The language rule is a bit complex, so I'll copy it verbatim from the standard:
[conv.prom]
A prvalue of an unscoped enumeration type whose underlying type is not fixed can be converted to a prvalue of the first of the following types that can represent all the values of the enumeration ([dcl.enum]): int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int. If none of the types in that list can represent all the values of the enumeration, a prvalue of an unscoped enumeration type can be converted to a prvalue of the extended integer type with lowest integer conversion rank ([conv.rank]) greater than the rank of long long in which all the values of the enumeration can be represented. If there are two such extended types, the signed one is chosen.
May I ask if my guess is correct?
In this case, int
is a correct guess because it is the first type in the list of the quoted standard rule, and it can represent all values of the enum which are 0, 1 2. But the guess does not apply universally.
CodePudding user response:
From the C 17 Standard (7.6 Integral promotions)
3 A prvalue of an unscoped enumeration type whose underlying type is not fixed (10.2) can be converted to a prvalue 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 10.2): int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int. If none of the types in that list can represent all the values of the enumeration, a prvalue of an unscoped enumeration type can be converted to a prvalue of the extended integer type with lowest integer conversion rank (7.15) greater than the rank of long long in which all the values of the enumeration can be represented. If there are two such extended types, the signed one is chosen.
4 A prvalue of an unscoped enumeration type whose underlying type is fixed (10.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
and (16.3.3.2 Ranking implicit conversion sequences)
(4.2) — 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.
So as the conversion to the fixed underlying type char is better than the conversion of the underlying type char to int due to the integral promotions then the function with the parameter char is selected as the most viable function.
I also noticed that if I don't explicitly specify the underlying type for ABC, then ff(int) will be called.
Because according the quote #3 from the section "7.6 Integral promotions" the enumeration type is promoted at least to the type int
.
However if you will declare the enumeration for example the following way as shown in teh demonstration program below
#include <iostream>
#include <limits>
enum ABC
{
a = std::numeric_limits<unsigned int>::max(), b, c
};
void ff( char c )
{
std::cout << "char\n";
}
void ff( int i )
{
std::cout << "int\n";
}
int main()
{
ff( a );
}
then the function call will be ambiguous.
CodePudding user response:
Specifying the underlying type for an enum will de facto set it as immediately compatible with this underlying type: that's why it's the char
version that is called in your example.
The implicit underlying type is, as usual in C/C , int
. It's what happens when you don't specify a type with your enum - so the int
version is called first.
But on my compiler (MSVC2017 when I tried), even the first case don't work: I got a "Call to 'ff' is ambiguous" for the char
version... Probably because ABC_char
is an enum
, not "really" a char, and not "really" an int, but is compatible with both through implicit cast.
Other calls to ff
don't produce any error - int
version is called without a single warning.
See below code:
#include <iostream>
enum ABC_char : char { a1, b1, c1 } ;
enum ABC_short : short int { a2, b2, c2 } ;
enum ABC_int { a3, b3, c3 } ;
void ff(char)
{
std::cout << "char" << std::endl ;
}
void ff(int)
{
std::cout << "int" << std::endl ;
}
int main()
{
ff(a1) ; // Call to 'ff' is ambiguous
ff(a2) ; // short int -> int
ff(a3) ; // int -> int
}
The ff(a1)
instruction won't even compile. Compiler sees two candidate functions and don't assume any of the two.
But this code works for all calls/enums:
#include <iostream>
enum ABC_char : char { a1, b1, c1 } ;
enum ABC_short : short int { a2, b2, c2 } ;
enum ABC_int { a3, b3, c3 } ;
template < typename T > void ff ( T c )
{
std::cout << typeid(c).name() << " / " << typeid(std::underlying_type_t<T>).name() << std::endl ;
}
int main()
{
ff(a1) ; // enum ABC_char / char
ff(a2) ; // enum ABC_short / short
ff(a3) ; // enum ABC_int / int
}
You could, in template, use std::is_same
and some constexpr
conditions to ensure that you call the "correct" behavior for your function:
template < typename T > void ff2 ( T )
{
if constexpr (std::is_same<std::underlying_type_t<T>, char>()) {
std::cout << "Using char version" << std::endl ;
} else
if constexpr (std::is_same<std::underlying_type_t<T>, int>()) {
std::cout << "Using int version" << std::endl ;
} else {
std::cout << "I have luck!" << std::endl ;
}
}
int main()
{
ff2(a1) ; // Using char version
ff2(a2) ; // I have luck!
ff2(a3) ; // Using int version
}