I'm currently designing some functions that make use of SFINAE to control overload resolution. This is easy to get wrong, so I'd like to be able to write both positive and negative tests for what the API can be called with. For example:
// A templated function that should only be used via type deduction. (We
// don't want to let users to set template arguments explicitly in order
// to reserve the right to change them. They are not a public API.)
template <
int&... ExplicitArgumentBarrier,
typename T,
typename = std::enable_if<!std::is_same_v<T, int>>>
void AcceptVector(std::vector<T>);
// We should be able to feed the function most vectors.
static_assert(kCanCallAcceptVector<std::vector<double>>);
static_assert(kCanCallAcceptVector<std::vector<std::string>>);
// But not vector<int>.
static_assert(!kCanCallAcceptVector<std::vector<int>>);
It feels like I should be able to do this with std::is_invocable_v, but that requires us to explicitly name the object being called, which requires providing template arguments and which assumes something about the overload set. Instead I literally want to test overload resolution: regardless of how many overloads for AcceptVector there are and what their template arguments look like, is a call site spelled AcceptVector that provides a particular type as argument valid?
What's the best way to do this? Here is what I've come up with:
#define DEFINE_CAN_CALL_VARIABLE(namespace_name, function_name) \
template <typename... Args> \
struct internal_CanCall##function_name final { \
private: \
struct Functor { \
template <typename... U, \
typename = decltype(::namespace_name ::function_name( \
std::declval<U>()...))> \
void operator()(U&&... args); \
}; \
\
public: \
static constexpr bool kValue = std::is_invocable_v<Functor, Args...>; \
}; \
\
template <typename... Args> \
inline constexpr bool kCanCall##function_name = \
internal_CanCall##function_name<Args...>::kValue;
DEFINE_CAN_CALL_VARIABLE(my::project, AcceptVector);
The idea is to define a functor that we can name for std::is_invocable_v, and then lean on overload resolution in that functor. But there are some downsides:
I have to package it into a macro in order to make it reusable without having the same problem as
std::is_invocable_vover again.I need to have the user provide the namespace as a macro argument in order to avoid problems due to argument-dependent lookup. (Although I said I'm interested in any call spelled
AcceptVector, I guess I really mean any call spelledAcceptVectorthat resolves to anAcceptVectorfunction in my project's namespace.)I'm not 100% sure I haven't missed other edge cases.
Does it seem like this is correct? Is there a less awful way to do this?