views:

242

answers:

2

How do I require and check that an argument is a certain concept in C++?

For example, the random_shuffle function in the algorithm header requires that its arguments are RandomAccessIterators:

  template<typename _RandomAccessIterator>
    inline void
    random_shuffle(_RandomAccessIterator __first, _RandomAccessIterator __last)
    {
      // concept requirements
      __glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
            _RandomAccessIterator>)
      __glibcxx_requires_valid_range(__first, __last);

      if (__first != __last)
        for (_RandomAccessIterator __i = __first + 1; __i != __last; ++__i)
          std::iter_swap(__i, __first + (std::rand() % ((__i - __first) + 1)));
    }

I guess I can't use these __glibcxx_function_requires etc. in my own code? How do they work? Do you check things like that in your code?

+6  A: 

Boost has a library for this. It's probably easier and more well documented than figuring out how to use the version your STL implementer has hacked together.

http://www.boost.org/doc/libs/1%5F40%5F0/libs/concept%5Fcheck/concept%5Fcheck.htm

Andy
Thanks! Haha, sounds almost like: There's an app for that!
A: 

These macros are defined away unless you define _GLIBCXX_CONCEPT_CHECKS. So I tried to compile this both ways:

#include <list>
#include <algorithm>

int main()
{
    std::list<int> li;
    std::random_shuffle(li.begin(), li.end());
}

Without concept checks: 8 lines of error - which instantiation, where, no match for operator. All is clear.

With concept checks: the same errors + 50 lines or so of "instantiated from here" gibberish ending with "error: conversion from 'std::bidirectional_iterator_tag' to non-scalar type 'std::random_access_iterator_tag' requested". Doh! I could have figured that out faster if there was a comment with the random_shuffle algorithm.

One thing is that you don't necessarily need these checks. The code will fail to compile if the requirements are not met. And it seems to me that a simple "no matching operator<" could be clearer than 50 lines of gibberish concluding (in obscure wording) that "function requires less-than-comparable concept".

If you want those checks (for things like random_shuffle), one way is to forward the call to another function that also accepts the right iterator tag:

#include <list>
#include <algorithm>
#include <iterator>
namespace detail {
template <class Iter>
void shuffle(Iter first, Iter last, std::random_access_iterator_tag)
{
    if (first != last)
        for (Iter i = first + 1; i != last; ++i)
            std::iter_swap(i, first + (std::rand() % ((i - first) + 1)));
}
}

template <class Iter>
void shuffle(Iter first, Iter last)
{
    detail::shuffle(first, last, typename std::iterator_traits<Iter>::iterator_category());
}

int main()
{
    std::list<int> li;
    shuffle(li.begin(), li.end());
}

produces a nice message: "no matching function for call to 'shuffle(std::_List_iterator&, std::_List_iterator&, std::bidirectional_iterator_tag)'"

And if you want to be über-nice, you can add a templated overload, that asserts at compile-time (static_assert comes with C++0x):

template <class Iter, class Tag>
void shuffle(Iter, Iter, Tag )
{
    //test some template-dependent expression that is always false
    //to avoid it from firing unless the function is instantiated
    static_assert(sizeof(Tag) == 0, "shuffle requires random access iterators");
}

Not saying that boost's concept checks don't have their place.

UncleBens