Reputation: 20774
I want to define a function template:
template<typename T>
void foo(T arg)
But I want T
to match only certain types. Specifically, T
should derive (maybe through multiple inheritance) form a certain base class. Otherwise this template shouldn't be included in the overload set.
How can I do this?
Upvotes: 6
Views: 2987
Reputation: 275740
In C++1z with concepts lite, you can do this:
template<class T>
requires std::is_base_of<Foo, T>{}()
void foo(T arg) {
}
under the current (experimental) implementation. Which is pretty clean and clear. There may be a way to do something like:
template<derived_from<Foo> T>
void foo(T arg) {
}
but I haven't worked it out. You can definitely do:
template<derived_from_foo T>
void foo(T arg){
}
where we have a custom concept called derived_from_foo
that applies iff the type is derived from foo
. What I don't know how to do is template concepts -- concepts generated from template type parameters.
In C++14, here are two methods. First, normal SFINAE:
template<class T,
class=std::enable_if_t<std::is_base_of<Foo, T>{}>
>
void foo(T arg) {
}
here we create a template that deduces the type T
from its argument. It then tries to deduce its second type argument from the first argument.
The second type argument has no name (hence class=
), because we are only using it for a SFINAE test.
The test is enable_if_t< condition >
. enable_if_t< condition >
generates the type void
if condition
is true. If condition
is false, it fails in "the immediate context", generating a substitution failure.
SFINAE is "Substitution failure is not an error" -- if your type T
generates a failure in the "immediate context" of the function template signature, this doesn't generate a compile-time error, but instead results in the function template not being considered a valid overload in this case.
"Immediate context" is a technical term here, but basically it means the error has to be "early enough" to be caught. If it requires compiling bodies of functions to find the error, that is not in "the immediate context".
Now, this isn't the only way. I personally like hiding my SFINAE code behind a gloss of respectability. Below, I use tag dispatching to "hide" the failure somewhere else, instead of putting it right up front in the function signature:
template<class T>
struct tag {
using type=T;
constexpr tag(tag const&) = default;
constexpr tag() = default;
template<class U,
class=std::enable_if_t<std::is_base_of<T,U>{}>
>
constexpr tag(tag<U>) {}
};
struct Base{};
struct Derived:Base{};
template<class T>
void foo( T t, tag<Base> = tag<T>{} ) {
}
here we create a tag
dispatch type, and it allows conversion to base. tag
lets us worth with types as values, and use more normal C++ operations on them (instead of template-like metaprogramming <>
s all over the place).
We then give foo
a second argument of type tag<Base>
, then construct it with a tag<T>
. This fails to compile if T
is not a derived type from Base
.
The nice thing about this solution is that the code that makes it not work seems more intuitive -- tag<Unrelated>
cannot convert to tag<Base>
. This does not, however, prevent the function from being considered for overload resolution, which can be a problem.
A way with less boiler plate is:
template<class T>
void foo( T t, Base*=(T*)0 ) {
}
where we use the fact that pointers can be converted iff there is a derivation relationship between them.
In C++11 (and without constexpr
support), we first write a helper:
namespace notstd {
template<bool b, class T=void>
using enable_if_t=typename std::enable_if<b,T>::type;
}
then:
template<class T,
class=notstd::enable_if_t<std::is_base_of<Foo, T>::value>
>
void foo(T arg) {
}
if you don't like the helper, we get this ugly extra:
template<class T,
class=typename std::enable_if<std::is_base_of<Foo, T>::value>::type
>
void foo(T arg) {
}
the second C++14 technique above can also be translated to C++11.
You can write an alias that does the test if you want:
template<class U>
using base_test=notstd::enable_if_t<std::is_base_of<Base, U>::value>;
template<class T,
class=base_test<T>
>
void foo(T arg) {
}
Upvotes: 4
Reputation: 303377
Use SFINAE with std::is_base_of
:
template <typename T,
typename = std::enable_if_t<
std::is_base_of<Foo, T>::value
>>
void foo(T arg);
That will only include foo
in the overload set if T
inherits from Foo
. Note that this includes ambiguous and inaccessible bases as well. If you want a solution that only allows for T
s that inherit publicly and unambiguously from Foo
, then you can instead use std::is_convertible
:
template <typename T,
typename = std::enable_if_t<
std::is_convertible<T*, Foo*>::value
>>
void foo(T arg);
Note the reversal of arguments.
Regardless of which form you pick, it can be aliased for brevity:
template <typename T>
using enable_if_foo = std::enable_if_t<std::is_base_of<Foo, T>::value>;
template <typename T,
typename = enable_if_foo<T>>
void foo(T arg);
This works because std::enable_if
has a nested type named type
if and only if the boolean passed in is true
. So if std::is_base_of<Foo, T>::value
is true
, enable_if_t
gets instantiated to void
, as if we had written:
template <typename T,
typename = void>
void foo(T arg);
But, if T
does not inherit from Foo
, then the type trait will evaluate as false
, and std::enable_if_t<false>
is a substitution failure - there is no typename enable_if<false>::type
. You might expect this to a compile error, but substitution failure is not an error (sfinae). It's just a template deduction failure. So the effect is that foo<T>
is simply removed from the set of viable overload candidates in this case, no different from any other template deduction failure.
Upvotes: 13
Reputation: 30604
SFINAE based techniques such as the following;
template <typename T,
typename Test = std::enable_if_t<std::is_base_of<Foo, T>::value>>
void foo(T arg);
Are good to remove the function from the overload list - which would be a general case.
If you wish to keep the function in the list, and if chosen as the best overload to fail if the type matches some criteria (such as a base requirement here), the static_assert
can be used;
template <typename T>
void foo(T arg)
{
static_assert(std::is_base_of<Foo, T>::value, "failed type check");
// ...
}
Upvotes: 5