Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What to use as replacement for concepts (upcoming feature) in C++?

What to use as replacement for concepts (upcoming feature) in C++?

You might have heard of concepts in C++. It is a feature that will allow you to specify requirements on types in templates.

I am looking for a way to do this now, and the best I found is in Stroustrup's book where he uses predicates together with static_assert like this:

template<typename Iter, typename Val>
Iter find(Iter b, Iter e, Val x) 
{
    static_assert(Input_iterator<Iter>(),"find(): Iter is not a Forward iterator");

    // Rest of code...
}

Please let me know if you use other methods or if something is wrong with this one.

like image 600
user3111311 Avatar asked Feb 03 '14 21:02

user3111311


Video Answer


3 Answers

Well, the few times I've needed concept-like features, I turned to Boost Concept Check. It's not the prettiest library, but it seems to have a lot of stuff already built in.

The only issue I'd have using your method is that one needs to write all the trait classes. I've not used it extensively, but presumably a lot of common stuff is already done for you with Boost.

like image 130
KitsuneYMG Avatar answered Nov 15 '22 08:11

KitsuneYMG


There is a C++03 way to do part of the compile-time checking that concepts provide.

Checking if a specific member is provided

Concepts can be defined as follows (The if(0) is used to suppress errors during linking. The (void)test# is used to suppress unused variable warnings.):

template <class T>
struct ForwardIterator {
  ForwardIterator() {
    if(0) {
      void (T::* test1) () = &T::operator++; (void)test1;
    }
  }
};

template <class T>
struct BidirectionalIterator {
  BidirectionalIterator() {
    if(0) {
      ForwardIterator<T> requirement_1;
      void (T::* test1) () = &T::operator--; (void)test1;
    }
  }
};

And can be tested for at compile time using template instantiation:

struct FooIterator {
  void operator++() {}
};

template struct BidirectionalIterator<FooIterator>;

It has the added benefit of giving compile errors that (once you're used to them) are much better readable than those provided by C++11's static_assert. For example, gcc gives the following error:

concept_test.cpp: In instantiation of ‘BidirectionalIterator<T>::BidirectionalIterator() [with T = FooIterator]’:                                                                                                          
concept_test.cpp:24:17:   required from here                                                                                                                                                                               
concept_test.cpp:15:30: error: ‘operator--’ is not a member of ‘FooIterator’                                                                                                                                               
       void (T::* test1) () = &T::operator--; (void)test1;                                                                                                                                                                 
                              ^                                                                                                                                                                                            

Even MSVC2010 generates a useful compile error that includes what value of the template parameter T caused the error. It does not do that for static_asserts.

Checking the types of arguments and return values

If parameter/return types depend on the class being tested, the tested class must provide the necessary typedefs. For example, the following concept tests whether a class provides begin and end functions that return a forward iterator:

template <class T>
struct ForwardIterable {
  ForwardIterable() {
    if(0) {
      ForwardIterator<typename T::Iterator> requirement_1;
      typename T::Iterator (T::* test1) () = &T::begin; (void)test1;
      typename T::Iterator (T::* test2) () = &T::end;   (void)test2;
    }
  }
};

And it is used as follows (note that the typedef is necessary):

struct SomeCollection {
  typedef FooIterator Iterator;
  Iterator begin();
  Iterator end();
};

template struct ForwardIterable<SomeCollection>;

Signature checking

This method also extensively checks the signature. In the following code, the compiler would detect that the argument of modifyFooItem should not be const.

struct SomeFoo;

template <class T>
struct TestBar {
  TestBar() {
    if(0) {
      int (T::* test1) (SomeFoo * item) = &T::modifyFooItem; (void)test1;
    }
  }
};

struct SomeBar {
  int modifyFooItem(const SomeFoo * item) {}
};

template struct TestBar<SomeBar>;

It generates the following error:

concept_test.cpp: In instantiation of ‘TestBar<T>::TestBar() [with T = SomeBar]’:
concept_test.cpp:61:17:   required from here
concept_test.cpp:52:47: error: cannot convert ‘int (SomeBar::*)(const SomeFoo*)’ to ‘int (SomeBar::*)(SomeFoo*)’ in initialization
       int (T::* test1) (SomeFoo * item) = &T::modifyFooItem; (void)test1;
like image 31
bcmpinc Avatar answered Nov 15 '22 07:11

bcmpinc


The best way to check for concepts is to use substitution failure. However, in C++98 detection using substitution failure is fairly limited. In C++11, we can use substitution failure with expressions which is much more powerful. The Tick library in C++11 provides simple way to define a concept predicate. For example, a quick and dirty is_input_iterator could be written like this:

TICK_TRAIT(is_input_iterator,
    std::is_copy_constructible<_>)
{
    template<class I>
    auto requires_(I&& i) -> TICK_VALID(
        *i,
        ++i,
        i++,
        *i++
    );
};

And then Tick also provides a TICK_REQUIRES macro to add template constraints(which just takes care of all the enable_if boilerplate), so you can just define the function like this:

template<typename Iter, typename Val, TICK_REQUIRES(is_input_iterator<Iter>())>
Iter find(Iter b, Iter e, Val x) 
{
    // Rest of code...
}

Ideally, you don't want to use static_assert since it produces a compile error. So we can't detect if say find, when called with certain parameters, is valid, since it would produce a compiler error when not valid.

like image 28
Paul Fultz II Avatar answered Nov 15 '22 07:11

Paul Fultz II