Что использовать в качестве замены для понятий (предстоящая особенность) в C ++?
Возможно, вы слышали о концепциях в C ++. Это особенность, которая будет
позволяют указывать требования к типам в шаблонах.
Я ищу способ сделать это сейчас, и лучшее, что я нашел в
Книга Страуструпа, где он использует предикаты вместе со static_assert следующим образом:
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...
}
Пожалуйста, дайте мне знать, если вы используете другие методы или что-то не так с этим.
Ну, несколько раз, когда я нуждался в концептуальных функциях, я обращался к Boost Concept Check. Это не самая красивая библиотека, но, похоже, в ней уже есть много встроенного.
Единственная проблема, с которой я столкнулся бы при использовании вашего метода, заключается в том, что нужно написать все классы признаков. Я не использовал его широко, но, вероятно, для вас уже сделано много общего с Boost.
Существует C ++ 03 способ выполнить часть проверки во время компиляции, которую предоставляют концепции.
Концепции могут быть определены следующим образом ( if(0)
используется для подавления ошибок при компоновке. (void)test#
используется для подавления предупреждений о неиспользуемых переменных.):
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;
}
}
};
И может быть проверено во время компиляции с использованием шаблона:
struct FooIterator {
void operator++() {}
};
template struct BidirectionalIterator<FooIterator>;
У него есть дополнительное преимущество, заключающееся в том, что он дает ошибки компиляции, которые (как только вы к ним привыкли) гораздо лучше читаются, чем те, которые предоставляет static_assert в C ++ 11. Например, gcc выдает следующую ошибку:
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;
^
Даже MSVC2010 генерирует полезную ошибку компиляции, которая включает то, какое значение параметра шаблона T вызвало ошибку. Это не делает это для static_asserts.
Если параметры / возвращаемые типы зависят от тестируемого класса, тестируемый класс должен предоставить необходимые typedefs. Например, следующая концепция проверяет, предоставляет ли класс функции начала и конца, которые возвращают прямой итератор:
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;
}
}
};
И он используется следующим образом (обратите внимание, что typedef необходим):
struct SomeCollection {
typedef FooIterator Iterator;
Iterator begin();
Iterator end();
};
template struct ForwardIterable<SomeCollection>;
Этот метод также тщательно проверяет подпись. В следующем коде компилятор обнаружит, что аргумент modifyFooItem
не должно быть постоянным.
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>;
Генерирует следующую ошибку:
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;
Лучший способ проверить концепцию — использовать ошибку замещения. Однако в C ++ 98 обнаружение с использованием ошибки замещения довольно ограничено. В C ++ 11 мы можем использовать подстановку с выражениями, которые гораздо более мощные. Поставить галочку библиотека в C ++ 11 предоставляет простой способ определения концептуального предиката. Например, быстрый и грязный is_input_iterator
можно написать так:
TICK_TRAIT(is_input_iterator,
std::is_copy_constructible<_>)
{
template<class I>
auto requires_(I&& i) -> TICK_VALID(
*i,
++i,
i++,
*i++
);
};
А потом Поставить галочку также обеспечивает TICK_REQUIRES
макрос для добавления шаблонных ограничений (который просто заботится обо всех enable_if
шаблон), так что вы можете просто определить функцию следующим образом:
template<typename Iter, typename Val, TICK_REQUIRES(is_input_iterator<Iter>())>
Iter find(Iter b, Iter e, Val x)
{
// Rest of code...
}
В идеале вы не хотите использовать static_assert
так как он выдает ошибку компиляции. Таким образом, мы не можем обнаружить, если, скажем, find
при вызове с определенными параметрами является допустимым, так как это приведет к ошибке компилятора, если оно недопустимо.