If your name, like mine, isn’t Scott Meyers, or perhaps it is, but you’re not the one Scott Meyers, you will undoubtedly have made a mistake or two when programming. And by the way, while we’re on Scott Meyers, if you haven’t read his books you most certainly should do that right now. In fact, you’re not allowed to continue reading until you have.
So you’ve made a mistake or two while programming. If you’re unlucky like me, you’ve also been writing templatized code with errors in them. Compilers generally do not output pretty error messages when you do.
Such an error is fairly often not neither a syntax error, nor error in your templte code, but because the type you’re passing to your function or class simply does not support the ‘implied’ requirements of that templatized method. What is an implied requirement then? Glad you asked.
Consider the following code.
template<typename T>
T divide (T & numerator, T & denominator)
{
return numerator/denominator;
}
Looks good enough. Let’s try to use our templatized method.
double dn = 1
double dd = 2
int in = -5;
int id = 7;
string sn = "Hello";
string sd = "World";
divide(dn,dd); //Fine
divide(in,id); //Fine
divide(sn,sd); //Fails with a horrible horrible error message.
Now you’re thinking. You silly man! You can’t divide strings. Well, how am I supposed to know that? And why is that error message so long, tedious and hard to decipher? (Try compiling it yourself). It’d be nice if I could get a nice error message.
Enter Concepts.
Concepts are a bit like interfaces in the Java or C# sense, but for templates.
To call our templatized divide method, our type needs to support operator/. So let’s define a concept, and have our method require it.
auto concept Dividable<typename T>
{
T operator/ (const T &, const T &);
}
template<typename T> requires Dividable<T>
T divide (T & numerator, T & denominator)
{
return numerator/denominator;
}
I know dividable is not the right word, but it should be. Divisible sounds like you can observe an object in the locations simultaneously which is, needless to say, absolutely preposterous. I’m sticking to dividable.
Concepts are not completely unlike classes in many cases. You can use inheritance with them, require support for multiple methods, and even require support for axioms! The axioms however, are not enforced by the compiler (as it would be impossible in many cases to verify its validity) but merely serve as optimisation hints for your compiler.
As an example of using an axiom let’s define the mathematical concept of a group the c++0x way. If you don’t remember your definitions, (who does?) here’s a handy link.
concept Group<typename Operator, typename T>
{
T operator() (Operator, T, T);
axiom Associativity(Operator oper, T a, T b, T c)
{
oper(oper(a,b),c) == oper(a,oper(b,c));
}
axiom Identity(Operator oper, T a)
{
oper(a::identity,a) == oper(a,a::identity);
}
//The (reverse element) axiom is impossible to test!
}
A group is called abelian if it is commutative as well. So while we’re at it, let’s define a concept for that too.
concept AbelianGroup<typename Operator, typename T> : Group<Operator,T>
{
axiom Commutativity(Operator oper, T a, T b)
{
oper(a,b) == oper(b,a);
}
}
You might have noticed I used the auto keyword for my Dividable concept, but not the Group concepts. Specifying the auto keyword just means that any type that implicitly supports the concept is considered to have implemented it. If we omit the keyword then types have to explicitly declare that they support a concept if you want them to be used with a method that requires a concept.
If you want a good read on the subject, here’s the proposed standard as of March 2009