mercredi 30 octobre 2019

Handling of switch enum class returns in clang, gcc and icc consistently

I am generally using clang to develop code, using all reasonable warnings I can (-Wall -Wextra [-Wpedantic]). One of the nice things about this setup is that the compiler checks for the consistency of the switch stataments in relation to the enumeration used. For example in this code:

enum class E{e1, e2};

int fun(E e){
    switch(e){
        case E::e1: return 11;
        case E::e2: return 22; // if I forget this line, clang warns
    }
}

clang would complain (warn) if I omit either the e1 or the e2 case, and if there is no-default case.

<source>:4:12: warning: enumeration value 'e2' not handled in switch [-Wswitch]
    switch(e){

This behavior is great because

  1. it checks for consistency at compile time between enums and switches, making them a very useful and inseparable pair of features.
  2. I don't need to define an artificial default case for which I wouldn't have a good thing to do.
  3. It allows me to omit a global return for which I wouldn't have a good thing to return (sometimes the return is not a simple type like int, it could be a type without a default constructor for example.

(Note that I am using an enum class so I assume only valid cases, as an invalid case can only be generated by a nasty cast on the callers end.)

Now the bad news: Unfortunately this breaks down quickly when switching to other compilers. In GCC and Intel (icc) the above code warns (using the same flags) that I am not returning from a non-void function.

<source>: In function 'int fun(E)':
<source>:11:1: warning: control reaches end of non-void function [-Wreturn-type]
   11 | }
      | ^
Compiler returned: 0

The only solution I found for this working to both have a default case and return a non-sensical value.

int fun(E e){
    switch(e){
        case E::e1: return 11;
        case E::e2: return 22;
        default: return {}; // or int{} // needed by GCC and icc
    }
}

This is bad because of the reasons I stated above (and not even getting to the case where the return type has no default constructor). But it is also bad because I can forget again one of the enum cases and now clang will not complain because there is a default case.

So what I ended up doing is to have this ugly code that works on these compilers and warns when it can for the right reasons.

enum E{e1, e2};

int fun(E e){
    switch(e){
        case E::e1: return 11;
        case E::e2: return 22;
#ifndef __clang__    
        default: return {};
#endif
    }
}

or

int fun(E e){
    switch(e){
        case E::e1: return 11;
        case E::e2: return 22;
    }
#ifndef __clang__    
    return {};
#endif
}

Is there a better way to do this?

This is the example: https://godbolt.org/z/h5_HAs


In the case on non-default constructible classes I am really out of good options completely:

A fun(E e){
    switch(e){
        case E::e1: return A{11};
        case E::e2: return A{22};
    }
#ifndef __clang__
    return reinterpret_cast<A const&>(e);  // :P, because return A{} would be invalid
#endif
}

https://godbolt.org/z/3WC5v8

Aucun commentaire:

Enregistrer un commentaire