📜 ⬆️ ⬇️

Boost concepts

Personally, the lack of standard mechanisms for setting parameter constraints has always scared me of using templates in C ++. In other words, when a developer writes a function

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

he makes various assumptions about the functionality of objects of type T , but does not have the standard ability to convey them to users. So the following 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 an open method T :: someCheck with no parameters, which returns a value that is cast to logical type
  3. There is an open method T :: someAction , which can take a single numeric parameter.

Problem

Now, let's say, the programmer decided to distribute someFunc as a library. How can its user know about the limitations?
  1. Reading library documentation. If it is, and clearly written. But even in this case, no one will read the documentation of all used libraries before each change of their code. Remember all the conditions by heart, too, not everyone can.
  2. Examine library source code. Also a lesson for an amateur. Moreover, the larger the library and its project more difficult, 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, looking for why ... However, those who used C ++ templates know what error messages may look like. On anything, just not on the hint of the form "Fix it right here, and it will work." Sometimes the message is clear 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 doesn’t matter that the original use context is not restored there.

Consider an example (we will return to it later)
')
Need to sort the list (standard container). Nothing foreshadows, we write

 std::list<int>theList; 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 the template argument for 'std :: move_iterator <_RanIt> &' from 'std :: _ List_iterator <std :: _ List_val <std :: _ List_simple_types <int >>>' c: \ program files (x86) \ microsoft visual studio 12.0 \ vc \ include \ algorithm 3157 1 MyApp

But this is half the trouble - when you click on an error, we find ourselves in the depths of the standard algorithm library here in this place

 template<class _RanIt, class _Pr> 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); } 

The first reaction: “What? Why the vector was sorted, but the list suddenly does not exist - both containers have iterators, both are aware of the order of the elements .. ”And okay, the standard library is also beaten up, and programmers usually know what happened. But imagine that you were thrown into the bowels of another, less well-known library like this without a life buoy ...

Decision

It turns out that there is a solution. There is an initiative to change the language in this direction, but so far it has not reached the 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, supplies a description of the concepts necessary for their proper operation. The user can automatically test all their entities for compliance with the proposed rules. In this case, errors will already be much clearer, of the form: The class does not support the concept “There must be a default constructor” .

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

Consider an example for the function someFunc given at the beginning of the article. The first rule is that the presence of the copy constructor is covered by the finished concept of boost :: CopyConstructible ; for the rest, you will have to write tests manually.

 #include <boost/concept_check.hpp> template <class T> struct SomeFuncAppropriate { public: BOOST_CONCEPT_ASSERT((boost::CopyConstructible<T>)); 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, which uses the test type as a parameter. Checking for compliance with ready-made concepts is carried out using the macro BOOST_CONCEPT_ASSERT. Please note that as a parameter, the concept in brackets is passed to it, as a result, double brackets are required, although they hurt the eye.

Custom checks can be implemented using the BOOST_CONCEPT_USAGE macro. It is important to remember that all the instances involved in testing (we have T t ) must 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 BOOST_CONCEPT_ASSERT macro. 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

 BOOST_CONCEPT_ASSERT((SomeFuncAppropriate<SomeClass>)); 

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 at the broken line in the definition of the 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': '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); } ... 

Moreover, the cursor points to the line

  TT a(b); // require copy constructor 

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

Let's return to the example of std :: list sorting with std :: sort . We express in the form of a concept the requirements for the container being sorted. First, std :: sort can only work with containers that support random access. The corresponding concept is in boost ( boost :: RandomAccessContainer ), but 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 helps out boost with the finished concept of boost :: LessThanComparable .
We combine concepts into one

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

Run the check

 BOOST_CONCEPT_ASSERT((Sortable<std::list<int> >)); 

We see
error C2676: binary '[': 'const std :: list <int, std :: allocator <_Ty >>' boost \ concept_check.hpp

Clicking on the error sends us to the source code of the RandomAccessContainer concept, making it clear that it was the one that was broken. If you replace std :: list with std :: vector , the concept check is successful. Now let's try to check the sortability of the vector of SomeClass instances.
 BOOST_CONCEPT_ASSERT((Sortable<std::vector<SomeClass> >)); 

The container is now suitable, but you can’t sort it anyway, since SomeClass does not define a “less” operator. We will know about it immediately.
error C2676: binary '<': 'SomeClass'

Clicking on the error - and we find ourselves in the source LessThanComparable , understanding what exactly violated.

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

Source: https://habr.com/ru/post/210838/


All Articles