最后的手段/ catch-all / fallback模板重载

时间:2015-02-09 11:01:08

标签: c++ templates overload-resolution

我之前提出的问题很明显, Overload resolution, templates and inheritance,将在重载之前选择模板重载,这需要派生到基本的转换。

但是,有没有办法提供一个后备重载,只有在没有其他匹配的情况下才会选择作为绝对的最后手段?在这种特殊情况下,可以使用enable_if,但不幸的是,这不可扩展。

像这样:

// 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());

在这种情况下,我希望调用UserBaseType重载,而不是模板重载。

2 个答案:

答案 0 :(得分:6)

如果您愿意要求您的用户通过Argument Dependent Lookup (ADL)提供他们的自定义点,您可以使用众所周知的额外间接层来完成此操作。首先,可以通过提供最差的可能后退并确定名称查找是否选择它来确定给定名称的ADL是否成功[*]:

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>()))>;
}

由于省略号转换严格比每个[over.ics.rank] / 2的标准或用户定义的转换序列更差,因此库用户提供的foo的任何合理定制都将更好地匹配

然后,您需要一些机制来根据has_adl_foo特征在您的后备实施和用户提供的自定义之间进行调度:

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));
}

然后,用户可以相当简单地提供他们的自定义 - 与库命名空间中的专门化模板相比,简单 - 通过在其类声明的命名空间中声明foo重载,ADL可以在其中找到它们DEMO ):

struct UserType {};
struct DerivedUserType : UserType {};

void foo(const UserType&) {
  std::cout << "User extension\n";
}

<子> [*]:ADL检测技术改编自@T.C.'s answerWhat is a proper way to implement is_swappable to test for the Swappable concept?

答案 1 :(得分:3)

保证优先级低于其他任何参数的唯一参数是C风格的可变参数:...,这肯定不是您想要(或甚至能够)使用的。

我担心没有什么可以提供唯一的用户端自定义提供过载的地方。但是,如果您可以承受更高的用户负担,那么您可以使用特质类:

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 */}

然后,库的用户必须为所有适用的类专门化HasCustomFoo

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

它并非完全自动化,但至少解决方案在用户手中,并且库可以保持通用。