Reputation: 52273
When using the compile-time duck typing inherent with the template style, is there any way to enforce the requirement that the template argument implements certain methods with certain signatures?
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();
}
Of course, even without this, there is perfect type safety: if the template argument T
does not have a method used in the template function implementation, the compiler will always complain.
But I find it appealing to state clearly that class T
must have all the methods specified in some class ProtocolT
. It would allow me to constrain the design earlier in the development process by requiring methods from T
that I don't yet use in the template function implementation.
Even if I didn't include any unused methods in ProtocolT
, I still think a verified protocol conformance would help when I need to write a class usable as T
. (Of course, no one stops me from writing ProtocolT
for documentation purposes, but then the compiler won't validate that ProtocolT
includes at least all the required methods.)
Upvotes: 4
Views: 808
Reputation: 25317
The feature you are looking for is known as concepts. They are currently a technical specification; GCC has an implementation of concepts lite.
Using concepts would look something like (I'm not too familiar with the syntax, so it would probably be slightly different):
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();
}
However, if you want a solution you can use right now, you can emulate concepts with a variety of techniques.
You could write type-traits to detect if a function does what you want.
You could also use the detection idiom, which abstracts the previous technique, greatly reducing boilerplate. For your example:
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>;
In using it, you could either be SFINAE friendly and SFINAE off of meets_protocol_v
, or you could static assert:
template <typename T>
void f(const T& x) {
static_assert(meets_protocol_v<T>, "Doesn't meet protocol");
x.g();
}
Upvotes: 4
Reputation: 37587
Maybe inserting corresponding static_assert
:
static_assert
(
::std::is_same< void, decltype(::std::declval< T >().h()) >::value
, "T must implement void h(void)"
);
Also note that in your example when T follows ProtocolT
requirements it still won't work because f
accepts a const
reference to T, while ProtocolT
only says it should have non-const g()
.
Upvotes: 2