Channels ▼
RSS

C/C++

Checking Concept Without Concepts in C++


Properties to Check

The C++0x <type_traits> header provides a fairly extensive set of basic type properties to check -- is a template parameter a function or a class, does one type derive from another, does a class have a default constructor, or a copy constructor, can you convert an instance of one type to another, and so on. You can use these in isolation, or use a combination to build more complex concepts.

There are however many useful properties that are not covered by the basic type traits. In particular, they provide no way of checking whether or not a class has a particular member type, or member function. This is quite a useful facility, since many complex concepts rely on the types that meet the concept having particular member functions. The following section will walk through how to detect the presence or absence of particular class members.

Detecting Class Members

The C++0x standard provides the std::lock_guard<> class template, which encapsulates ownership of a mutex lock. The constructor locks the mutex, and the destructor unlocks the mutex. It is a class template, so that it can work with any kind of mutex or lockable object -- anything that has lock() and unlock() member functions. std::lock_guard<MyCustomMutex> is fine, but std::lock_guard<int> is not.

Since std::lock_guard is a simple template, the error messages you get from std::lock_guard<int> will be fairly clear, but it would be nice to be able to give a nice clear signal: int is not a lockable object. Therefore I'll walk though the process of building a concept check for lockable objects.

To be usable with std::lock_guard, there are only two requirements on our lockable type: It must have a lock() member function that takes no parameters, and an unlock() member function that takes no parameters. If we can detect one, we can detect the other, since the requirements are almost identical. So, let's start by defining a simple template to check whether our type has a lock member function:


template<typename T>
struct has_lock_member_function;

Like all the standard type traits, we will derive our class from std::true_type if the type T meets our requirements, and from std::false_type if it doesn't.

First things first then: T can only have a member function if it's a class type. Fundamental types can't have member functions, so if it's not a class type it can't have a lock member function. Let's say that in code with a bit of delegation:


template<typename T>
struct has_lock_member_function:
    std::is_class<T>
{};

That's all very well as far as it goes, but that says that all classes have a lock member function, which is blatantly false. Let's split the definition with a specialization:


template<typename T,bool is_class_type=std::is_class<T>::value>
struct has_lock_member_function:
    std::false_type
{};

template<typename T>
struct class_has_lock_member_function_returning_void_with_no_params
{
    static const bool value=???;
};

template<typename T>
struct has_lock_member_function<T,true>:
    std::integral_constant<bool,
    class_has_lock_member_function_returning_void_with_no_params<T>::value>
{};

The primary template will take care of the case where is_class_type is False; if is_class_type is True we fall through to the specialization. Now all we need to do is figure out what to put in the class_has_lock_member_function_returning_void_with_no_params<T> class.

Here's the trick: You can use SFINAE to choose between two overloaded functions. If the overloaded functions return types of different sizes, then you can check the size of the result to identify which overload was called.


typedef char small_type;
struct large_type
{
    small_type dummy[2];
};

template<typename T>
struct class_has_lock_member_function_returning_void_with_no_params
{
    template<void (T::*)()> struct tester;

    template<typename U>
    static small_type has_matching_member(tester<&U::lock>*); // A
    template<typename U>
    static large_type has_matching_member(...); // B
    
    static const bool value=sizeof(has_matching_member<T>(0))==sizeof(small_type);
};

This uses SFINAE twice over: firstly to discard types that don't have a member lock, and second to check the signature.

The overload of has_matching_member marked A will be discarded by SFINAE if T::lock is a type member, or T doesn't actually have a lock member, since &T::lock would be invalid when substituted into has_matching_member<T> . For such types only the overload marked B is viable, so sizeof(has_matching_member<T>(0)) is sizeof(large_type) and value will be False.

If &T::lock is valid then the signature is checked against the non-type template parameter of the tester class template. This requires that the type is exactly pointer-to-member-function-of-T-taking-no-parameterss-and-returning-void. If it's anything else, then the signature won't match and overload A is discarded by SFINAE just as if there was no lock member at all. If T::lock is a data member, or a static member function, or returns the wrong type, or takes parameters, then the types won't match and the overload will be discarded.

This can readily be extended to cover additional cases by changing thesignature required for the tester class template. You could even allow for multiple valid signatures by adding further overloads of has_matching_member that instantiated different class templates, such as:


    template<void (*)()> struct tester2;

    template<typename U>
    static small_type has_matching_member(tester2<&U::lock>*);

which would allow for T::lock to be a static member function. Our corresponding test for try_lock is thus:


template<typename T>
struct class_has_try_lock_member_function_returning_bool_with_no_params
{
    template<bool (T::*)()> struct tester;

    template<typename U>
    static small_type has_matching_member(tester<&U::try_lock>*); // A
    template<typename U>
    static large_type has_matching_member(...); // B
    
    static const bool value=sizeof(has_matching_member<T>(0))==sizeof(small_type);
};

Neat as it is, this trick is not perfect. If all the lock members of the class being tested are private or the matching lock member is private then this will result in a compilation error, since access violations are always fatal. This is inconvenient, but not necessarily a common problem.

The second problem is that this test requires an exact match for the signature, as we already saw. If the member function is inherited from a base class, then the corresponding pointer-to-member will not be an exact match -- it will be a pointer-to-member-of-base rather than a pointer-to-member-of-T. The trait will thus return False, even though you could call lock() on a an instance of T. Likewise, it requires that the return type is actually void, whereas if we're discarding the return value it doesn't matter what the return type is.

Though this is harsh, it is not an insurmountable problem -- you can either specialize the template for your types, or wrap types with a template that provides exactly-matching signatures.

The techniques of which I am aware that can handle broader inexact matches such as imprecise return types or inherited member functions are brittle in the face of multiple matching member functions, or indeed multiple overloaded member functions where none match, causing compilation failures in such cases. I feel this is an acceptable trade-off -- matching is strict, but mismatch results in a value of false rather than a compilation error.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video