最后 resort/catch-all/fallback 模板重载
Last resort/catch-all/fallback template overload
正如我之前提出的问题所表明的那样,
,将在需要派生到基础转换的重载之前选择模板重载。
但是,有没有一种方法可以提供后备过载,如果没有其他匹配项,仅将其选作绝对的最后手段?在这种特殊情况下,可以使用 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 重载,而不是模板重载。
唯一保证优先级低于其他参数的参数是 C 风格的可变参数:...
,这肯定不是您想要(甚至不能)使用的。
恐怕没有什么可以提供的,唯一的用户端自定义会提供过载。但是,如果您可以容忍用户的负担稍高一些,则可以使用特征 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 */}
然后,库的用户必须专门化 HasCustomFoo
所有适用的 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
它不是全自动的,但至少解决方案掌握在用户手中并且库可以保持通用。
如果您愿意要求您的用户通过 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));
}
然后,用户可以相当简单地提供他们的定制 - 无论如何,与库命名空间中的专用模板相比简单 - 通过在 ADL 可以找到它们的 class 声明的命名空间中声明 foo
重载(DEMO):
struct UserType {};
struct DerivedUserType : UserType {};
void foo(const UserType&) {
std::cout << "User extension\n";
}
[*]:ADL 检测技术改编自 @T.C.'s answer to What is a proper way to implement is_swappable
to test for the Swappable concept?。
正如我之前提出的问题所表明的那样,
但是,有没有一种方法可以提供后备过载,如果没有其他匹配项,仅将其选作绝对的最后手段?在这种特殊情况下,可以使用 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 重载,而不是模板重载。
唯一保证优先级低于其他参数的参数是 C 风格的可变参数:...
,这肯定不是您想要(甚至不能)使用的。
恐怕没有什么可以提供的,唯一的用户端自定义会提供过载。但是,如果您可以容忍用户的负担稍高一些,则可以使用特征 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 */}
然后,库的用户必须专门化 HasCustomFoo
所有适用的 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
它不是全自动的,但至少解决方案掌握在用户手中并且库可以保持通用。
如果您愿意要求您的用户通过 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));
}
然后,用户可以相当简单地提供他们的定制 - 无论如何,与库命名空间中的专用模板相比简单 - 通过在 ADL 可以找到它们的 class 声明的命名空间中声明 foo
重载(DEMO):
struct UserType {};
struct DerivedUserType : UserType {};
void foo(const UserType&) {
std::cout << "User extension\n";
}
[*]:ADL 检测技术改编自 @T.C.'s answer to What is a proper way to implement is_swappable
to test for the Swappable concept?。