Как обеспечить соблюдение формального протокола с помощью шаблонов C++?

При использовании утиной печати во время компиляции, присущей стилю шаблона, есть ли способ принудительно требование, чтобы аргумент шаблона реализовывал определенные методы с определенными сигнатурами?

struct ProtocolT {
  void g() const;
  void h();
}

// I want the compiler to check that T conforms to ProtocolT
// that is, T must implement g() and h() rather than just g()
template <typename T>
void f(const T& x) {
  x.g();
}

Конечно, и без этого безупречная безопасность типов: если аргумент шаблона T не имеет метода, используемого в реализации функции шаблона, компилятор всегда будет жаловаться.

Но я считаю привлекательным четко заявить, что class T должен иметь все методы, указанные в каком-то class ProtocolT. Это позволило бы мне ограничить дизайн на ранних этапах процесса разработки, потребовав методы из T, которые я еще не использую в реализации функции шаблона.

Даже если бы я не включил неиспользуемые методы в ProtocolT, я все же думаю, что проверенное соответствие протоколу поможет, когда мне нужно будет написать класс, пригодный для использования как T. (Конечно, никто не мешает мне написать ProtocolT для документации, но тогда компилятор не будет проверять, что ProtocolT включает в себя по крайней мере все необходимые методы.)


person max    schedule 04.07.2017    source источник
comment
наследовать от ProtocolT для обеспечения реализации, а затем SFINAE из T.   -  person Hatted Rooster    schedule 04.07.2017
comment
Относится к does-static-polymorphism-make-sense-for- реализация-интерфейса   -  person Jarod42    schedule 04.07.2017


Ответы (2)


Функция, которую вы ищете, называется concepts. В настоящее время они являются технической спецификацией; GCC имеет облегченную реализацию концепций.

Использование понятий будет выглядеть примерно так (я не слишком знаком с синтаксисом, поэтому, вероятно, он будет немного другим):

template <typename T>
concept bool Protocol = requires(const T a, T b) {
  { a.g() } -> void;
  { b.h() } -> void;
};

void f(const Protocol& x) {
  x.g();
}

Однако если вам нужно решение, которое вы можете использовать прямо сейчас, вы можете эмулировать концепции с помощью различных методов.

Вы можете написать признаки типа, чтобы определить, делает ли функция то, что вы хотите.

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

template <typename T>
using g_t = decltype(std::declval<const T&>().g());

template <typename T>
using h_t = decltype(std::declval<T&>().h());

template <typename T>
constexpr bool meets_protocol_v = std::experimental::is_detected_exact_v<void, g_t, T>
            && std::experimental::is_detected_exact_v<void, h_t, T>;

При его использовании вы можете либо быть дружественным к SFINAE и SFINAE от meets_protocol_v, либо вы можете статически утверждать:

template <typename T>
void f(const T& x) {
  static_assert(meets_protocol_v<T>, "Doesn't meet protocol");
  x.g();
}
person Justin    schedule 04.07.2017

Возможно, вставив соответствующий static_assert:

static_assert
(
    ::std::is_same< void, decltype(::std::declval< T >().h()) >::value
,   "T must implement void h(void)"
);

Также обратите внимание, что в вашем примере, когда T следует требованиям ProtocolT, он все равно не будет работать, потому что f принимает ссылку const на T, а ProtocolT только говорит, что он должен иметь неконстантный g().

person user7860670    schedule 04.07.2017