Reputation: 7301
Is there a way to do something like the following in C++
template<typename TAnimal>
bool can_eat(TAnimal& animal) where bool TAnimal::IsAlive() exists
{
return !animal.IsAlive();
}
//...
Duck duck;
assert(can_eat(duck) == true); //compiles
Wood wood;
can_eat(wood); // fails to compile because wood doesn't have IsAlive()
The explicit interface, in my opinion, makes it more clear what the function expects. However, I don't want to create an actual interface class (very tedious).
Upvotes: 2
Views: 1837
Reputation: 35439
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.
Upvotes: 9
Reputation: 545488
Boost offers BOOST_STATIC_ASSERT
for this. The just recently approved version of the C++ standard will offer a built-in version of a static_assert
macro.
enable_if
, on the other hand, isn’t really well suited for this. It can be used but the primary purpose of enable_if
is to distinguish between otherwise ambiguous overloads.
Upvotes: 2
Reputation: 30035
As others have said this will just work. it won't be able to instantiate the template if the function does not exist.
The boost library contains some classes to assist with this kind of thing though, for example enable_if which can be used to only "enable" a template where a condition is true. There is also the type traits library which is kind of related, you might be able to use this to determine at compile time if the function you want to call exists.
I have to admit I've not used any of this myself, but it looks to me like you should be able to use it to achieve what you want...
Upvotes: 0
Reputation: 19339
This is something that concepts were intended to solve.
Concepts were a proposed addition to the latest C++ standard, but were dropped because the commitee wasn't convinced that they were solid enough to include in the language. See what Herb Sutter wrote about their exclusion from the standard.
Technically, concepts are unneeded, as template simply use whatever they can (i.e. lose the where
clause, and you have what you're asking for). If the required method isn't there at compile time, then the code simply will not compile. But concepts would give the coder more explicit control over the type's interface, and would give much more reasonable error message than currently provided by most compilers.
Upvotes: 5
Reputation: 103693
where void TAnimal::IsAlive() exists
I assume you mean bool TAnimal::IsAlive()
? If so, C++ already does what you are asking. If Duck has the IsAlive() method, then this will compile:
Duck duck;
assert(can_eat(duck) == true); //compiles
If Wood does not have the IsAlive() method, this will not compile:
Wood wood;
can_eat(wood); // fails to compile because wood doesn't have IsAlive()
That's what you're asking for right?
Upvotes: 1
Reputation: 249093
You don't have to do anything--just omit the hypothetical "where ... exists" from your example and it is normal C++ code that works.
If you insist on having the function be available only under some condition, you might try combining boost::enable_if
with has_member
from here: http://lists.boost.org/Archives/boost/2002/03/27229.php
The idea being that you would only allow the template function to be instantiated if some condition was met...but since SFINAE the compiler is basically going to do that for you already in the case where the condition is the same as the actual compile-time needs of the function (as in your example).
Upvotes: 0