A case was pointed out to me where this static iterator (you can skip that article and still understand the current one) would not work as expected, without having a compile or runtime error. Things would appear to work, but from a logical point of view, they could be incorrect.
The iterator can be passed data by several structs, each struct having an overload of a function to operate on it.
struct A; void operate(input::A& a); struct B; void operate(input::B& b);
For each object of these structs, the proper overload of
operate is called. But the following case could not work as expected:
struct A; void operate(input::A& a); struct B : A;
B inherits from
A and there is no
operate overload for
B. An expectation would be for the code to not compile because there is no function defined to operate on
B. But actually, object slicing comes into play:
B is a derived class and can be assigned to its base class
A. And objects of type
B will be operated on with
operate overload. If this is the intended behavior, it’s OK.
If you want to strictly control things and make sure you have defined all the required functions, you need a way to make sure you’re given an error. Ideally a compile-time error.
What I aimed for is to define each function only for a specific type and if I did not define one for a type, I should get a compile-time error. And I went to type traits. When I define an
operate overload, I want to clearly specify the type I define it for.
I have the type I want to define the function for and the type of the function’s argument. And I want the two types to be the same.
And I want each function to be enabled only if the two types mentioned above are the same.
template <typename T> std::enable_if_t<std::is_same<T, input::A>::value, void> operate(T& a); template <typename T> std::enable_if_t<std::is_same<T, input::B>::value, void> operate(T& b);
If I don’t define
B I get a compile-time error that tells me the exact problem:
error: no matching function for call to ‘operate(input::B&)’
What I can do better is to have an easier declaration of the overloads, so I wrote an alias for the types check:
template <typename T, typename T2> using op = std::enable_if_t<std::is_same<T, T2>::value, void>; template <typename T> op<T, input::A> operate(T& a); template <typename T> op<T, input::B> operate(T& b);
This is C++14. The C++11 equivalent (which also works on C++14) just does not use the
template <typename T, typename T2> using op = typename std::enable_if<std::is_same<T, T2>::value, void>::type;
Although this implementation solves the issue, I’m not necessarily happy with the result because it looks a bit “heavy”.