As an example, I want to use a constraint to ensure that the function isinf
is implemented for a template parameter T
. If T
is one of float
, double
, long double
or an integral type, this can be done in the following way:
#include <cmath>
template <typename T>
concept Test = requires (T a) {
std::isinf(a);
};
However, I also want to use this constraint for custom, non-standard data types for which I implement my own isinf
function. This function is not contained in the std
namespace, so I tried the following instead:
#include <cmath>
template <typename T>
concept Test = requires (T a) {
using std::isinf;
isinf(a);
};
This does not work since every statement in the requires clause should be a valid requirement and using std::isinf
is not a "requirement" at all.
I see two workarounds to this problem:
Move the using std::isinf;
clause to the global namespace. But this introduces isinf
to the global namespace, which I'd like to avoid.
Encapsulate the using std::isinf;
clause with the concept definition in a namespace named ABC
, then add using ABC::Test;
directly after the namespace. This seems a little bit weird.
Is there a better solution?
The way this sort of thing works in Ranges is by creating a Customization Point Object. This closely mirrors your second option (we stick a using-declaration in a custom namespace) except we also provide an mechanism for users to call the correct isinf
without having to write a bunch of the same kind of boilerplate themselves.
A customization point object for isinf
would look something like this:
namespace N {
// make our own namespace
namespace impl {
// ... where we can bring in std::isinf
using std::isinf;
struct isinf_t {
// our type is constrained on unqualified isinf working
// in a context where std::isinf can be found
template <typename T>
requires requires (T t) {
{ isinf(t) } -> std::same_as<bool>;
}
constexpr bool operator()(T t) const {
// ... and just invokes that (we know it's valid and bool at this point)
return isinf(t);
}
};
}
// we provide an object such that `isinf(x)` incorporates ADL itself
inline constexpr auto isinf = impl::isinf_t{};
}
And now that we have an object, a concept follows directly:
template <typename T>
concept Test = requires (T t) {
N::isinf(t);
}
This is precisely how the range
concept is specified.