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
- Objects of type T are passed by value, which means they must have an open copy constructor.
- There is an open method T :: someCheck with no parameters, which returns a value that is cast to logical type
- 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?
- 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.
- 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) {
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);
Moreover, the cursor points to the line
TT a(b);
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!