Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Last resort/catch-all/fallback template overload

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.

like image 874
Emil Eriksson Avatar asked Feb 09 '15 11:02

Emil Eriksson


2 Answers

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?.

like image 177
Casey Avatar answered Oct 13 '22 19:10

Casey


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.

like image 38
Angew is no longer proud of SO Avatar answered Oct 13 '22 19:10

Angew is no longer proud of SO