При использовании утки во время компиляции, присущей шаблон стиля, Есть ли способ обеспечить требование, чтобы аргумент шаблона реализовывал определенные методы с определенными сигнатурами?
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
включает в себя как минимум все необходимые методы.)
Функция, которую вы ищете, известна как концепции. В настоящее время они являются технической спецификацией; GCC имеет реализацию концепций Lite.
Использование концепций выглядело бы примерно так (я не слишком знаком с синтаксисом, поэтому, вероятно, он будет немного другим):
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>;
Используя его, вы можете быть либо СФИНАЕ дружественным и СФИНАЕ от meets_protocol_v
или вы могли бы статически утверждать:
template <typename T>
void f(const T& x) {
static_assert(meets_protocol_v<T>, "Doesn't meet protocol");
x.g();
}
Может быть, вставив соответствующий static_assert
:
static_assert
(
::std::is_same< void, decltype(::std::declval< T >().h()) >::value
, "T must implement void h(void)");
Также обратите внимание, что в вашем примере, когда T следует ProtocolT
Требования это все еще не будет работать, потому что f
принимает const
ссылка на Т, в то время как ProtocolT
только говорит, что должно быть неконстантным g()
,