wtorek, 29 lipca 2014

Undesirable implicit conversions

Recently Andrzej Krzemienski published a post on his blog about inadvertent conversions. Basically the problem is that sometimes compiler performs implicit conversions (and, as of C++ standard it is allowed to perform at most one) that aren't desired by programmers. In my post I'd like to summarize all solutions that I'm aware of with ones that appeared in comments.

Classical source of such undesirable conversions are non-explicit constructors accepting exactly one non-default parameter. However, in this example there's a need to disable particular converting constructors while still accepting desired ones. Consider following piece of code:

1 struct C {
2     C(int const n) {}
3 };
4 
5 int main(void) {
6     C fromInt = 5;      // Okay
7     C fromDouble = 5.5; // Potentially not desired
8 }

This kind of problem occurs in boost::rational library, as Andrzej mentions. Okay, so what are the solutions? The first one is pretty obvious - let's explicitly delete all constructors accepting double as parameter.

1 struct C {
2     C(int const n) {}
3     C(double const x) = delete;
4 };
5 
6 int main(void) {
7     C fromInt = 5;      // Okay
8     C fromDouble = 5.5; // Illegal!
9 }

The solution is pretty straightforward and it addresses the problem basing on type, not type traits - which can be a problem in some contexts. There is another solution that lacks this particular problem. It employs enable_if to make sure only integral types can be used with our converting constructors.

 1 #include <type_traits>
 2 using namespace std;
 3 
 4 struct C {
 5     template <typename T, typename enable_if<is_integral<T>::value, int>::type = 0>
 6     C(T const n) {}
 7 };
 8 
 9 int main(void) {
10     C fromInt = 5;      // Okay
11     C fromDouble = 4.0; // Illegal!
12 }

It is elegant and it does the job well. However, in my humble opinion there's one area of improvement - diagnostics and readability. If we used static_assert error message produces by the compiler would be a lot more readable to the programmer. Also, the code would become more clear and the intentions would be visible at first sight.

1 #include <type_traits>
 2 using namespace std;
 3 
 4 struct C {
 5     template <typename T>
 6     C(T const n) {
 7         static_assert(is_integral<T>::value, "Only integral values are supported!");
 8     }
 9 };
10 
11 int main(void) {
12     C fromInt = 5;
13 
14     // error: static_assert failed "Only integral values are supported!"
15     C fromDouble = 4.0;
16 }

Do you know any issues that can appear with last solution? If yes please share them in comments, please ;)

Interestingly, when you compile example from the first listing on Clang (3.5, trunk 196051) you'll get an warning that implicit conversion is being used. GCC (4.8.2) stays quiet about that. This is another proof that clang is more user-friendly compiler.




Brak komentarzy:

Prześlij komentarz