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;