Boost Concepts

  • Tutorial
From the use of templates in C ++, I personally was always scared by the lack of standard mechanisms for setting parameter restrictions. In other words, when a developer writes a function

template 
bool someFunc(T t)
{
	if (t.someCheck()) {
		t.someAction(0);
	}
}

he makes various assumptions regarding the functionality of objects of type T , however, he does not have the standard ability to convey them to users. So the above example assumes at least the following

  1. Objects of type T are passed by value, which means they must have an open copy constructor
  2. There is a public method T :: someCheck with no parameters, which returns a boolean value
  3. There is an open method T :: someAction , which can take one parameter that can be converted to a number type

Problem

Now, let's say the programmer decided to distribute someFunc as a library. How can its user know about existing restrictions?
  1. Reading library documentation. If it is and clearly written. But even in this case, no one will read the documentation of all the libraries used before each change to their code. Remembering all the conditions by heart is also not for everyone to handle.
  2. Studying the source code of the library. Also a lesson for an amateur. Moreover, the larger the library and the more complex the project, the less lovers

There remains one more option - in fact, the only automatic one - to build on compilation errors. Those. made a change, not going to, looking for why ... However, those who used the C ++ templates know what the error messages may look like. For anything, but not for a prompt like “Fix it here, and it will work.” Sometimes the message is understandable enough, and sometimes you find yourself in the wilds of someone else’s library ... The compiler reports an error in the place where it occurred - it does not matter to him that the original context of use can no longer be restored there.

Let's consider an example (we will come back to it later).

We need to sort the list (standard container). Nothing portends, we write

std::listtheList;
std::sort(theList.begin(), theList.end());

Not compiled. In VS2013, the error is as follows
error C2784: 'unknown-type std :: operator - (std :: move_iterator <_RanIt> &, const std :: move_iterator <_RanIt2> &)': could not deduce template argument for 'std :: move_iterator <_RanIt> &' from 'std :: _ List_iterator>> 'c: \ program files (x86) \ microsoft visual studio 12.0 \ vc \ include \ algorithm 3157 1 MyApp

But this is not so bad - when we click by mistake, we find ourselves in the depths of the standard algorithm library right here

template inline
	void sort(_RanIt _First, _RanIt _Last, _Pr _Pred)
	{	// order [_First, _Last), using _Pred
	_DEBUG_RANGE(_First, _Last);
	_DEBUG_POINTER(_Pred);
	_Sort(_Unchecked(_First), _Unchecked(_Last), _Last - _First, _Pred);
	}

First reaction: “What ?! Why the vector was sorted, but the list suddenly didn’t - both containers have iterators, both know about the order of the elements .. ”Well, the standard library is okay - this example is beaten, and programmers usually know what happened. But imagine that you were thrown into the bowels of another, not so famous library like this without a life buoy ...

Decision

It turns out that there is a solution. Language change initiative in this direction there , but so far I have not got standard.
But the boost library supports the concept of concepts (concepts), with which you can create custom constraints for template parameters.

The algorithm for using concepts is as follows. The developer, together with his libraries, provides a description of the concepts necessary for their correct operation. The user can automatically test all of his entities for compliance with the proposed rules. In this case, the errors will already be much clearer, of the form: The class does not support the concept of "There must be a default constructor . "

Using boost, the developer is not obliged to construct concepts from scratch every time - the library contains prefabricated basic restrictions .

Consider the example for the someFunc function at the beginning of the article. The first rule - the availability of a copy constructor is covered by the ready-made boost :: CopyConstructible concept , for the rest you have to write tests manually.

#include 
template 
struct SomeFuncAppropriate {
public:
	BOOST_CONCEPT_ASSERT((boost::CopyConstructible));
	BOOST_CONCEPT_USAGE(SomeFuncAppropriate)
	{
		bool b = t.someCheck();// метод someCheck, с возвращаемым значением, приводимым к bool
		t.someAction(0);// метод someAction с параметром, приводимым к числу
	}
private:
	T t; // must be data members
};

So, the concept of boost is a template structure, the parameter of which is the tested type. Check for compliance with ready-made concepts through the macro BOOST_CONCEPT_ASSERT. Please note - as a parameter, the concept in brackets is passed to him, as a result, double brackets are required, although they hurt the eye.

Custom checks can be implemented using the macro BOOST_CONCEPT_USAGE. It is important to remember that all instances involved in testing (we have T t ) should be declared as members of the class , and not as local variables.

When a concept is declared, it can be checked for compliance using the same macro BOOST_CONCEPT_ASSERT. Let's say we have a class

class SomeClass
{
public:
	SomeClass();
	void someCheck();
	int someAction(int);
private:
	SomeClass(const SomeClass& other);
};

You can test it like this

BOOST_CONCEPT_ASSERT((SomeFuncAppropriate));

We try to run - we immediately get an error
error C2440: 'initializing': cannot convert from 'void' to 'bool'

And when you click on it, we are thrown to the broken line in the definition of SomeFuncAppropriate concept (in BOOST_CONCEPT_USAGE), where you can easily understand the cause of the problem - the someCheck method returns void instead of bool . Corrects, try again ...
error C2248: 'SomeClass :: SomeClass': cannot access private member declared in class 'SomeClass' boost \ concept_check.hpp

By clicking on the error we find ourselves in the source code of the concept

  BOOST_concept(CopyConstructible,(TT))
  {
    BOOST_CONCEPT_USAGE(CopyConstructible) {
      TT a(b);            // require copy constructor
      TT* ptr = &a;       // require address of operator
      const_constraints(a);
      ignore_unused_variable_warning(ptr);
    }
...

And the cursor points to the line

 TT a(b);            // require copy constructor

Oh yes - the copy constructor is hidden. We fix it - now the test passes (the file is compiled with BOOST_CONCEPT_ASSERT). This means that the SomeClass class fully meets the expectations of the developer of the someFunc function . Even if changes are added in the future that violate compatibility, a concept check will immediately tell you what the problem is.

Back to the std :: list sorting example using std :: sort . We express the requirements for the sortable container as a concept. Firstly, std :: sort can only work with containers that support random access. The corresponding concept is in boost ( boost :: RandomAccessContainer), however, it is not enough. There is also a requirement for the contents of the container - its elements must support the comparison operator "less." Here again, boost helps with the ready concept of boost :: LessThanComparable .
We combine concepts into one

template 
struct Sortable 
{
	public:
		typedef typename std::iterator_traits::value_type content_type;
		BOOST_CONCEPT_ASSERT((boost::RandomAccessContainer));
		BOOST_CONCEPT_ASSERT((boost::LessThanComparable));
};

Run the test

BOOST_CONCEPT_ASSERT((Sortable >));

We see
error C2676: binary '[': 'const std :: list> 'does not define this operator or a conversion to a type acceptable to the predefined operator boost \ concept_check.hpp

A click on the error sends us to the source code of the RandomAccessContainer concept , making it clear that it is it that is violated. If you replace std :: list with std :: vector , checking the concept will succeed. Now let's try to check the sort vector of SomeClass instances .
BOOST_CONCEPT_ASSERT((Sortable >));

The container is now suitable, but it is still impossible to sort it, since SomeClass does not define a less-than operator. We will know about it right away
error C2676: binary '<': 'SomeClass' does not define this operator or a conversion to a type acceptable to the predefined operator boost \ boost \ concept_check.hpp

A click on the error - and we find ourselves in the source code LessThanComparable , realizing what exactly they violated.

Thus, concepts make generalized programming in C ++ a little less extreme. What can not but rejoice!

Also popular now: