Do not use enable_if
to enforce your requirements. Using enable_if
will make the function 'disappear', which can be quite confusing for the user. Typical symptom is an error message such as error: no matching function for call to expression
. That doesn't exactly convey to the user that a requirement violated.
You should instead enforce your requirements using static_assert
, assuming C++0x. If you're using C++03, whether you should be using an emulation of static_assert
(e.g. Boost's STATIC_ASSERT
) or not is a toss-up since that usually means trading one error message for the other.
Contrast:
// SFINAE for types that do not decay to int
template<
typename T
, typename = typename std::enable_if<
std::is_same<
typename std::decay<T>::type
, int
>::value
>::type
>
void
f(T&&)
{}
// using static assert instead
template<
typename T
>
void
g(T&&)
{
static_assert( std::is_same<typename std::decay<T>::type, int>::value
, "Constraints violation" );
}
Using GCC I get the following error for doing f("violation")
(both messages come with filename and line number):
error: no matching function for call to 'f(const char [10])'
On the other hand, g("violation")
yields:
error: static assertion failed: "Constraints violation"
Now imagine that you use clear, explicit messages in your assertions such as foo: parameter type must be CopyConstructible
inside template foo
.
With that said, SFINAE and static_assert
are somewhat antagonistic, thus having both explicit constraint violation messages and clever overloads isn't always possible and/or easy.
What you want to do is easily achieved using Boost.ConceptCheck. It does however require to write out-of-line code: the constraints class. I also don't think it uses static_assert
where available, so the error messages might not be as nice. This could be changed in the future.
Another possibility is to use static_assert
+ type traits. What's interesting with that approach is that with C++0x the library comes with a bevy of useful traits, which you can use right out of the box without writing out-of-line code. Even more interesting is that the use of traits is not limited to writing constraints, they can also be used with SFINAE to make clever overloads.
However, there is no trait that is available off-hand to check whether a type supports a particular member of operation, possibly due to the way C++ handles the names of functions. We can't use something like has_member<T, &T::member_to_test_for>
either because that would only make sense if the member we were testing for existed in the first place (disregarding things like overloads and the fact that we also need to pass the signature of the member to the trait).
Here's how to transform an arbitrary expression into a trait:
template<typename T>
struct void_ {
typedef void type;
};
template<typename T>
struct trait {
private:
typedef char yes[1];
typedef char no[2];
template<typename U>
static
yes&
test(U&&
, typename void_<decltype( std::declval<U&>().member() )>::type* = 0);
static
no&
test(...);
public:
static constexpr bool value = sizeof test(std::declval<T>()) == sizeof(yes);
};
Notice how sizable this is. Writing a Boost.ConceptCheck constraints class might be easier (but remember, not reusable for SFINAE).
The arbitrary expression is std::declval<U&>().member()
. Here, the requirements are that given an lvalue reference of U
(or T
for the case where the trait is true, if you will), then calling member()
on it is valid.
You could also check that the type of that expression (i.e. the result type of whatever overload of member
has been picked for this expression) is convertible to a type (do not check whether it is that type; that's too restrictive for no good reason). That would inflate the trait however, again making this in favour of a constraints class.
I do not know of a way to make a static_assert
part of the signature of a function template (this seems to be something you want), but it can appear inside a class template. Boost.ConceptCheck doesn't support that either.