Learning concept checking the hard way - P3
March 25, 2014
In the previous posts this and this, we looked at the basics of concept checking and stuff you need to know.
Finally here’s our has_less
method.
#include <iostream>
#include <vector>
#import <complex>
using namespace std;
template <typename T, typename unused = decltype(T() < T())>
std::true_type has_less_helper(const T&);
template <typename T>
std::false_type has_less_helper(...);
template <typename T>
constexpr bool has_less(void){
using my_type = decltype(has_less_helper<T>(std::declval<T>()));
return my_type::value;
}
template <typename T>
class BinarySearchTree{
public:
static_assert(
has_less<T>(),
"Assertion failed. No less than operation defined."
);
BinarySearchTree() {
}
};
int main(int argc, const char * argv[])
{
BinarySearchTree<int> binarySearchTree;
// Compile time error here, since complex<int> doesn't
// have a '<' operator.
BinarySearchTree<complex<int>> compBinarySearchTree;
return 0;
}
Let’s dissect the above method and see what’s happening.
has_helper
method has two possible overload options. For the int
datatype both are possible overload candidates but as we saw earlier
compiler prefers the non-variadic option. So it choosses the first
option which has a return type of std::true_type
.
However for complex<int>
, compiler has only one option since <
doesn’t exist for complex
type.
// we don't need to define the methods
// Since we are not calling them.
// we are just using the principle of SFINAE that applies to template
// arguments and not to the body
template <typename T, typename unused = decltype(T() < T())>
std::true_type has_less_helper(const T&);
//and
template <typename T>
std::false_type has_less_helper(...);
Note: true_type
and false_type
are just structs with values as
true
and false
resp. Check out their usage
We could directly use has_less_helper
to know what Type has a less
than operator defined for it. However it is important to note that the
has_less_helper
assumes that type T
has a default constructor
defined for it(decltype(T() < T())
).
To remove above dependency we could use declvalue(as discussed in
previous post). That is what we are
doing in has_less
method.
template <typename T>
constexpr bool has_less(void){
// using is equivalent to typedef
using my_type = decltype(has_less_helper<T>(std::declval<T>()));
// note that the return type is either true_type or false_type
return my_type::value;
}
Note: the constexpr
keyword makes the method evaluate at compile time. So we will have a true or false value from this method at compile time.
Inside our definition of generic BinarySearchTree
we put a
static_assert
(compile time assert) to check that we have a less that
operator define for the type T.
template <typename T>
class BinarySearchTree{
public:
static_assert(
has_less<T>(),
"Assertion failed. No less than operation defined."
);
BinarySearchTree() {
}
};
That’s it! Now when you declare something like
BinarySearchTree< complex<int> > compBinarySearchTree;
We get a compile time error.
Awesome! Now you understand the concept of concept checking. Happy coding.