There is a C ++ 03 method that does the part of the compilation time check provided by these concepts.
Check for a specific item
Concepts can be defined as follows ( if(0) used to suppress errors during binding. (void)test# used to suppress warnings about unused variables.):
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 it can be checked at compile time using a template instance:
struct FooIterator { void operator++() {} }; template struct BidirectionalIterator<FooIterator>;
It has the added benefit of providing compilation errors that (once you get used to them) are much better read than those provided by C ++ 11 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 compilation error, which includes what value of the template parameter T caused the error. This does not do for static_asserts.
Checking argument types and return values
If the parameters / return types depend on the class being tested, the class being tested should provide the necessary typedef. For example, the following concept checks if the class provides start and end functions that return the iterator forward:
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 typedef is required):
struct SomeCollection { typedef FooIterator Iterator; Iterator begin(); Iterator end(); }; template struct ForwardIterable<SomeCollection>;
Signature Verification
This method also verifies the signature extensively. In the following code, the compiler will find that the modifyFooItem argument modifyFooItem 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;