C ++ 11 — Что использовать в качестве замены для понятий (предстоящая особенность) в C ++?

Что использовать в качестве замены для понятий (предстоящая особенность) в 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...
}

Пожалуйста, дайте мне знать, если вы используете другие методы или что-то не так с этим.

7

Решение

Ну, несколько раз, когда я нуждался в концептуальных функциях, я обращался к Boost Concept Check. Это не самая красивая библиотека, но, похоже, в ней уже есть много встроенного.

Единственная проблема, с которой я столкнулся бы при использовании вашего метода, заключается в том, что нужно написать все классы признаков. Я не использовал его широко, но, вероятно, для вас уже сделано много общего с Boost.

1

Другие решения

Существует 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;
1

Лучший способ проверить концепцию — использовать ошибку замещения. Однако в 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при вызове с определенными параметрами является допустимым, так как это приведет к ошибке компилятора, если оно недопустимо.

1
По вопросам рекламы [email protected]