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 A
‘s 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.
std::is_same<T, T2>
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 operate
for 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 enable_if_t
alias:
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”.