(original) (raw)
Normally, inside switch statements duplicate case values are disallowed.
For instance, the following code
switch(mytype)
{
default:
case MY\_DUPLICATED\_DEFINE\_OR\_ENUM:
case 1: //error, 1 shadows above if MY\_DUPLICATED\_DEFINE\_OR\_ENUM also ==1
break;
}
yields an error message similar to
error: duplicate case value: '1' and 'MY\_DUPLICATED\_DEFINE\_OR\_ENUM' both equal '1'
case MY\_DUPLICATED\_DEFINE\_OR\_ENUM:
note: previous case defined here
In this example, the duplicated error reveals almost-certainly erroneous code.
However, clang and other compiles like gcc support case ranges.
switch(mychar)
{
case '!' ... '\~': return my\_isprint\_string(mychar);
}
But if you want to add an exception for say the quote character '"', you must instead split the ranges. So
switch(mychar)
{
case '!' ... ('"'-1)
case ('"'+1) ... '\~':
return my\_isprint\_string(mychar);
case '"': return "\\\\\\""; // escape quote
}
instead of
switch(mychar)
{
case '"': return "\\\\\\""; \[\[clang::badly\_named\_allow\_duplicate\_case\]\]
case '!' ... '\~': return my\_isprint\_string(mychar);
}
This simple example illustrates the basic problem. When you have multiple intersecting ranges, the problem becomes exacerbated.
Here's a slightly more complicated example:
// find width in bits
switch(myint64\_t)
{
// this won't compile
case INT8\_MIN ... INT8\_MAX: return 8;
case INT16\_MIN ... INT16\_MAX: return 16;
case INT32\_MIN ... INT32\_MAX: return 32;
}
Fixing the above, which would be natural with duplicate labels, requires splitting the cases into 5 ranges, each dependent on the values of the other.
switch(myint64\_t)
{
case INT8\_MIN ... INT8\_MAX: return 8;
case INT16\_MIN ... INT8\_MIN - 1: return: 16;
case INT8\_MAX + 1 ... INT16\_MAX: return 16;
case INT32\_MIN ... INT16\_MIN - 1: return 32;
case INT16MAX + 1 .. INT32\_MAX: return 32;
}
You can quickly see how interval math becomes more complicated. This is far less legible. In practice there are many more intervals to handle.
Allowing duplicate cases should be straightforward at the compiler level, however. The same methods that detect duplicates can be used to choose which duplicated case is executed. It doesn't matter which case statement, the first or the last, is considered to override so long as it is consistently applied.
This could be added either as an attribute on the switch or as an attribute on the individual cases. Something like "\[\[clang::badly\_named\_allow\_duplicate\]\]" or "\_\_attribute\_\_((badly\_named\_allow\_duplicate))"
For instance, the following code
switch(mytype)
{
default:
case MY\_DUPLICATED\_DEFINE\_OR\_ENUM:
case 1: //error, 1 shadows above if MY\_DUPLICATED\_DEFINE\_OR\_ENUM also ==1
break;
}
yields an error message similar to
error: duplicate case value: '1' and 'MY\_DUPLICATED\_DEFINE\_OR\_ENUM' both equal '1'
case MY\_DUPLICATED\_DEFINE\_OR\_ENUM:
note: previous case defined here
In this example, the duplicated error reveals almost-certainly erroneous code.
However, clang and other compiles like gcc support case ranges.
switch(mychar)
{
case '!' ... '\~': return my\_isprint\_string(mychar);
}
But if you want to add an exception for say the quote character '"', you must instead split the ranges. So
switch(mychar)
{
case '!' ... ('"'-1)
case ('"'+1) ... '\~':
return my\_isprint\_string(mychar);
case '"': return "\\\\\\""; // escape quote
}
instead of
switch(mychar)
{
case '"': return "\\\\\\""; \[\[clang::badly\_named\_allow\_duplicate\_case\]\]
case '!' ... '\~': return my\_isprint\_string(mychar);
}
This simple example illustrates the basic problem. When you have multiple intersecting ranges, the problem becomes exacerbated.
Here's a slightly more complicated example:
// find width in bits
switch(myint64\_t)
{
// this won't compile
case INT8\_MIN ... INT8\_MAX: return 8;
case INT16\_MIN ... INT16\_MAX: return 16;
case INT32\_MIN ... INT32\_MAX: return 32;
}
Fixing the above, which would be natural with duplicate labels, requires splitting the cases into 5 ranges, each dependent on the values of the other.
switch(myint64\_t)
{
case INT8\_MIN ... INT8\_MAX: return 8;
case INT16\_MIN ... INT8\_MIN - 1: return: 16;
case INT8\_MAX + 1 ... INT16\_MAX: return 16;
case INT32\_MIN ... INT16\_MIN - 1: return 32;
case INT16MAX + 1 .. INT32\_MAX: return 32;
}
You can quickly see how interval math becomes more complicated. This is far less legible. In practice there are many more intervals to handle.
Allowing duplicate cases should be straightforward at the compiler level, however. The same methods that detect duplicates can be used to choose which duplicated case is executed. It doesn't matter which case statement, the first or the last, is considered to override so long as it is consistently applied.
This could be added either as an attribute on the switch or as an attribute on the individual cases. Something like "\[\[clang::badly\_named\_allow\_duplicate\]\]" or "\_\_attribute\_\_((badly\_named\_allow\_duplicate))"
Kevin Lawler