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.
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.
There is a C++03 way to do part of the compile-time checking that concepts provide.
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.
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>;
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;
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With