27 September 2008

I believe every serious C++ programmer has used template more or less (at least the STL). I also believe that every one that used template would be "impressed" by compilation error messages for templates. An other issue of template compilation is that if it is not used, it is not instantiated and hence no error check. The Boost Concept Check Library (BCCL) somehow addressed these two issues.

In my opinion, the "concept" itself is an important concept of generic programming. Generic programming is to separate algorithms and data types. Algorithms are developed to handle data types comply with certain "concept", i.e. a set of traits. However, C++ programming language itself does not have "concept". Therefore, there is no built-in support of specifying "concept".

A good new is that SGI STL comes with g++ partly adopted BCCL and hence can be used for this purpose. Following examples demonstrate how to make use of it. Most of the examples come from bits/boost_concept_check.h and bits/concept_check.h (NOTE: all code snippets excerpted in the post are modified to better for the sake of simplicity.)

Check concept with a function

We found that std::min_element specifies concept requirement:

template<typename _ForwardIter>
  _ForwardIter
  min_element(_ForwardIter __first, _ForwardIter __last)
  {
    // concept requirements
    __glibcpp_function_requires(_ForwardIteratorConcept<_ForwardIter>)
    __glibcpp_function_requires(_LessThanComparableConcept<
      typename iterator_traits<_ForwardIter>::value_type>)
    // ...
  }

Follow the hint, we would see this:

// lgfang: the "ifndef" indicates that we must compile with
// "-D_GLIBCPP_CONCEPT_CHECKS" (unless we include bits/boost_concept_check.h
// directly cause that file does not contain such ifdef.

#ifndef _GLIBCPP_CONCEPT_CHECKS

#define __glibcpp_function_requires(...)
#define __glibcpp_class_requires(_a,_b)

#else // the checks are on

#include <bits/boost_concept_check.h>

#define __glibcpp_function_requires(...)                                 \
            __gnu_cxx::__function_requires< __gnu_cxx::__VA_ARGS__ >();
#define __glibcpp_class_requires(_a,_C)                                  \
            _GLIBCPP_CLASS_REQUIRES(_a, __gnu_cxx, _C);
#endif // enable/disable
namespace __gnu_cxx { // lgfang: it is regarded as a gnu extension

#define _IsUnused __attribute__ ((__unused__))
    // lgfang:__attribute__ ((__unused__)) is a gnu extension, tells compiler
    // NOT to report error when corresponding variable is not used.

template <class _Concept>
inline void __function_requires()
//lgfang: this is a function (hence the "function" in its name), therefore it
//can used (called) in functions but NOT in class definitions.
{
  void (_Concept::*__x)() _IsUnused = &_Concept::__constraints;
  // lgfang: defined a variable named __x, type of which is: member function of
  // class _Concept, accepts no parameter and returns nothing.

  // lgfang: we will not use __x, hence modify it with "__attribute__
  // ((__unused__))"

  // lgfang: defining __x is enough to make compiler to instantiate
  // _Concept::__constraints了. So, the remaining work is to make compiler
  // instantiate needed interfaces. Let us take LessThanComparableConcept as an
  // example.

  // lgfang: No extra operation needed, that is the run time performance impact
  // is minimum.
}

template <class _Tp>
struct _LessThanComparableConcept // lgfang: a concept class
{
  void __constraints() { // lgfang: must be this name
    __aux_require_boolean_expr(__a < __b); // lgfang: this the real requirement.
                                           // there are more examples below.
  }
  _Tp __a, __b;
    // lgfang: __a and __b must be member variables. If moved into
    // __constraints, a constraint is introduced: default constructor must be
    // available. See
    // http://www.boost.org/doc/libs/1_36_0/libs/concept_check/creating_concepts.htm
};

}

Check concept in class definitions

 1: template <typename _Tp, typename _Alloc = allocator<_Tp> >
 2:   class deque : protected _Deque_base<_Tp, _Alloc>
 3: {
 4:   // concept requirements
 5:   __glibcpp_class_requires(_Tp, _SGIAssignableConcept)
 6:   // ...
 7: }
 8: 
 9: #define __glibcpp_class_requires(_a,_C)                                  \
10:             _GLIBCPP_CLASS_REQUIRES(_a, __gnu_cxx, _C);
11: 
12: #define _GLIBCPP_CLASS_REQUIRES(_type_var, _ns, _concept) \
13:   typedef void (_ns::_concept <_type_var>::* _func##_type_var##_concept)(); \
14:   template <_func##_type_var##_concept _Tp1> \
15:   struct _concept_checking##_type_var##_concept { }; \
16:   typedef _concept_checking##_type_var##_concept< \
17:     &_ns::_concept <_type_var>::__constraints> \
18:      _concept_checking_typedef##_type_var##_concept
19: 
20: 
21: template <class _Tp>
22: struct _SGIAssignableConcept
23: {
24:   void __constraints() {
25:     _Tp __b(__a) _IsUnused;
26:     __a = __a;                        // require assignment operator
27:     __const_constraints(__a);
28:   }
29:   void __const_constraints(const _Tp& __b) {
30:     _Tp __c(__b) _IsUnused;
31:     __a = __b;              // const required for argument to assignment
32:   }
33:   _Tp __a;
34: };

Let's take a close look at the _GLIBCPP_CLASS_REQUIRES.

  • Line 12: This macro accepts 3 parameters. NOTE: this is different from __function_requires.
  • Line 13: typedef a type used below.
  • Line 14,15: a class template is defined.
  • Line 16-18: typedef one more type, which is not actually used. The purpose is to make the compiler to instantiate the class template just defined. This way, the check of concept is achieved.

NOTE: there is no run time operation involved. Therefore is no run time cost, only more compilation time.

Create our own 'concept'

Now that we understand how it works, we can now define our own "concept" (Check if the concept already exists in boost_concept_check.h first).

#include <bits/boost_concept_check.h>
// lgfang: boost_concept_check.h says: do not include it directly, but ...

using namespace std;

// lgfang: different macro names in different versions of g++, hence one more
// layer of encapsulation :(.
#define my_function_requires(...) __gnu_cxx::__function_requires<MySpace::__VA_ARGS__ >()
#define my_class_requires(a, C) _GLIBCXX_CLASS_REQUIRES(a, MySpace, C)

namespace MySpace {  // lgfang: must in a namespace. Not a big deal since I
                     // always put my code into namespaces anyway.
    template <typename T>
    struct HasValueConcept { // lgfang: use "struct" instead of "class" so that
                             // we needn't type "public:"
        void __constraints() {
            T b; // lgfang: ensure default constructor available.
            a.getVal(); // lgfang: ensure there is member function "getVal"
            // lgfang: we may restrict signature of the function if we want:
            // const char* (T::*x)() = &T::getVal;
            a.name; // lgfang: check member variable is also feasible. But,
                    // usually we should not impose such constraints in concept
                    // "has value".
        }
        T a;
    };

}

template <typename T>
void dumpVal (T& t) {
    // lgfang: check the concept in a function.
    my_function_requires(HasValueConcept<T>);
    t.getVal();
}

template <typename T>
struct C {
    // lgfang: check the concept in a class definition.
    my_class_requires(T, HasValueConcept);
    T a;
};

struct MyFun { // 修改这个类,看看编译器会报些什么错
    const char* getVal() {return "hello";}
    char* name;
};

int main(){
    MyFun obj;
    dumpVal(obj);
    C<MyFun> c;
    return 0;
}

// g++ testconcept.cc

Benefits

  • Compiler does more check.
  • Error message will be much more readable.
  • Requirement is more explicit.

Reference

For more comprehensive usage, please refer to boost document and SGI STL source code.



blog comments powered by Disqus