As is evident in a question I asked previously, Overload resolution, templates and inheritance, a template overload will be chosen before an overload which requires derived-to-base conversion.
However, is there a way to provide a fallback overload which is only selected as an absolute last resort if there is nothing else that matches? In this particular case enable_if
could be used but that would not be extensible, unfortunately.
Like this:
// My library has this and has no knowledge of the possible overloads of foo
template<typename T>
void foo(const T &) { /* Do something */ }
// The user of the library provides this:
void foo(const UserBaseType &) { /* Do something */ }
// User calls foo with object derived from UserBaseType:
foo(UserDerivedType());
In this case, I want the UserBaseType overload to be called, not the template overload.
If you're willing to require your users to provide their customization points via Argument Dependent Lookup (ADL), you can accomplish this with the proverbial additional layer of indirection. First, it is possible to determine if ADL for a given name succeeds by providing the worst possible fallback and determining if name lookup selects it[*]:
namespace detail {
// Simple trait that computes the inverse of std::is_same
template <typename, typename>
struct is_different : std::true_type {};
template <typename T>
struct is_different<T, T> : std::false_type {};
// The ellipsis conversion is worse than any other
// conversion, so overload resolution will choose
// this declaration of foo only if there is no
// result from ADL.
struct tag;
tag foo(...);
// Trait that determines if ADL for foo(T) succeeds.
template <typename T>
using has_adl_foo =
is_different<tag,decltype(foo(std::declval<T>()))>;
}
Since the ellipsis conversion is strictly worse than either a standard or user-defined conversion sequence per [over.ics.rank]/2, any reasonable customization of foo
provided by the library user will be a better match.
You then need some machinery to dispatch between your fallback implementation and a user-provided customization on the basis of the has_adl_foo
trait:
namespace detail {
// Fallback, used only if ADL fails.
template <typename T>
typename std::enable_if<!has_adl_foo<T>::value>::type
impl(T&&) {
std::cout << "Fallback\n";
}
// Dispatch to foo found by ADL.
template <typename T>
typename std::enable_if<has_adl_foo<T>::value,
decltype(foo(std::declval<T>()))
>::type
impl(T&& t) {
return foo(std::forward<T>(t));
}
}
template <typename T>
auto foo(T&& t) ->
decltype(detail::impl(std::forward<T>(t))) {
return detail::impl(std::forward<T>(t));
}
Users can then provide their customizations fairly simply - simple compared to specializing templates in your library namespace, anyway - by declaring foo
overloads in the namespace of their class declarations where ADL can find them (DEMO):
struct UserType {};
struct DerivedUserType : UserType {};
void foo(const UserType&) {
std::cout << "User extension\n";
}
[*]: ADL Detection technique adapted from @T.C.'s answer to What is a proper way to implement is_swappable
to test for the Swappable concept?.
The only parameter guaranteed to have lower precedence than anything else are C-style variadics: ...
, and that is certainly not what you'd want (or even be able to) use.
I am afraid there is nothing to provide where the only user-side customisation would be providing an overload. If you can tolerate a little bit higher burden on the user, though, you could make it work with a trait class:
template <class T>
struct HasCustomFoo : std::false_type
{};
template <class T, class Sfinae = typename std::enable_if<!HasCustomFoo<T>::value>::type>
void foo(const T &) { /* Do something generic */}
Then, the user of the library must specialise HasCustomFoo
for all applicable classes:
template <>
struct HasCustomFoo<UserBaseType> : std::true_type
{};
template <>
struct HasCustomFoo<UserDerivedType> : std::true_type
{};
void foo(const UserBaseType &) { /* Do something user-specific */ }
foo(UserDerivedType()); // This now calls the user-specific function
It's not fully automatic, but at least the solution is in the user's hands and the library can remain generic.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With