Template Constraints Are So 1997; Usher in C++09 Concepts

++03 has a very limited set of tools for enforcing constraints on templates. Consequently, templates code becomes messy, unintelligible, and buggy. With the addition of concepts to C++09, this unsavory state of affairs is about to change radically. Learn what concepts are and they can simplify template design and implementation.


You’re designing a template, but can’t find a simple and consistent way to specify usage policies and enforce constraints. Consequently, your template code becomes cluttered up and bug-prone.


Use concepts to enforce usage policies on templates. This will also allow the compiler catch usage violations earlier, and issue clearer diagnostics.

Sort It Out
Consider the following example of a perfectly valid program:

#include #include using namespace std;struct C {  void f();};int main(){ vector  vc; vb.push_back(C()); vb.push_back(C());}

This code is fine. However, when you add the following line to your program:

sort(vb.begin(), vb.end());//error

Your compiler barks at you:

[C++ Error] algorith.h(644): E2093 'operator<' not implemented in type 'C' for arguments of the same type.

The diagnostic doesn't really explain what the problem is. std::sort() has a usage constraint which isn't stated overtly in its declaration: "C shall be a type that has a less-than operator taking references to two references to C and returning bool".

In many other instances of violating an implicit template constraint you won't even get a compilation error; your program will simply have undefined behavior. Take, for example, auto_ptr. Standard Library containers have an implicit requirement that their objects shall be copy-constructible and assignable. auto_ptr is neither of these. Lo and behold, the instantiations of vector and dequecompile successfully:

vector  > va; //undefined behaviordeque  > da;//undefined behavior

In this case, the implicit requirement that auto_ptr shall be copy-constructible and assignable isn't enforced at compile time. It isn't expressed in the declarations of vector and dequeeither.

Generally speaking, the problem is that C++03 doesn't have a mechanism for expressing template constraints consistently and clearly.

Sordid? Out!
Concepts are a new C++09 feature. At least one compiler supports this feature already; other implementations will soon follow suit. Concepts work like this: You augment a template declaration with a set of policies and constraints called concepts. When a template is instantiated, the compiler checks whether the instantiation complies with that template's concepts. If all is well, the template is instantiated successfully. Otherwise, a compilation diagnostic is issued stating which constraints(s) were violated.

Let's look at a concrete example. The std::min()algorithm is defined as follows:

template //typical C++03 min()inline const T& min(const T& x, const T& y) { return x < y? x : y;}

As with std::sort(), std::min() imposes a constraint on its parameter T: "T shall be a type that has a less-than operator which takes two references to const T and returns bool." You express this requirement by embedding a concept directly in the definition of min():

template<LessThanComparable T> //note: no typename  const T& min(const T& x, const T& y) { return x < y? x : y;}

Instead of stating that T is an arbitrary type, this template definition states that T shall be LessThanComparable, whatever that is. If you instantiate min() with an argument that isn't LessThanComparable, the compiler will catch the error early and report that the argument of min() doesn't satisfy the LessThanComparablerequirement.

A concept definition uses the C++09 keyword conceptfollowed by the name of the concept and a template parameter list:

auto concept LessThanComparable  {/*...*/}  

The concept body contains a list of declarations that state the requirements with which the template parameter(s) must comply (I will explain the role of auto shortly). A complete definition of the LessThanComparableconcept looks like this:

auto concept LessThanComparable { bool operator<(T,T);};

LessThanComparable states that the template parameter T shall be a type that has a less-than operator which takes two T objects as arguments and returns bool. auto in this context indicates that any type that has an operator< will qualify as LessThanComparable. If you omit auto from this concept, users will have to explicitly state that their types are LessThanComparable by using a concept map, which I will not discuss here for the sake of brevity.

Suppose you need another concept called Regular that denotes any type can be constructed, copied, assigned to, compared, destroyed, and swapped. The Regular concept definition uses concept signatures, each of which denotes a different requirement:

auto concept Regular {//each signature denotes a different requirement T::T(); // constructible T::T(const T&); // copyable T& operator=(T&, const T&); // assignable T::˜T(); // destructible  bool operator==(const T&, const T&); //comparable void swap(T&, T&); // swappable};

Regulardenotes any type that:

  • Has all four canonical member functions
  • Can be compared using operator==
  • Can be swapped by calling swap()

std::string, int and vector qualify as Regular. However, std::ofstreamdoesn't because it's neither assignable nor copyable.

A concept can apply to more than one type. For instance, a Convertible concept guarantees that there shall be an implicit conversion from T to U:

auto concept Convertible  {operator U(const T&);};

Such a concept is typically used in a where-clause. The convert() function template uses a where-clause to ensure that its argument is convertible to the return type:

template  where ConvertibleU convert(const T& t) { return t;}float f=convert(5);//OK, int to floatfloat* p=convert (6); //error, int to float*  

Apart from the where-clause, this is an ordinary C++03 template.

There's More to Concepts
The concepts proposal includes other features that enable you not just to enforce requirements on template parameters but to write complete algorithms using concepts, or alter the implementation of an existing interface, for example implementing a stack using a vector by matching a Stack concept with a concept map that maps std::vector operations with std::stackoperations. Future 10 Minute Solutions will be dedicated to such advanced concepts features.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: