重载解析如何工作
首先我们进行名称查找和模板类型推导。为了foo(d)
,这给了我们两个选择:
-
foo(Derived const&)
, with [C = Derived]
foo(Base const&)
这些是我们可行的候选人。
然后我们确定哪一个重载是最佳可行的候选者。首先通过查看转换序列来完成此操作。在这种情况下,(1) 是精确匹配,而 (2) 涉及具有转换排名的派生到基本转换(请参阅这张桌子)。由于排名较差,候选者 (1) 是首选,因此被认为是最佳可行候选者。
如何做自己真正想做的事
最简单的方法是简单地添加第三个重载Derived
:
foo(Derived const&)
这里,(1)和(3)在转换顺序方面是等效的。但不是模板的函数比模板的函数更受青睐(将其视为选择最具体的选项),因此将选择 (3)。
但你不想那样做。所以选项是: make (1) 不适用于Derived
或使 (2) 为Derived
也以一种首选的方式。
禁用通用模板
我们可以使用 SFINAE 简单地排除源自Base
:
template <class T, class = std::enable_if_t<!std::is_convertible<T*, Base*>::value>
void foo(T const& ); // new (1)
void foo(Base const& ); // same (2)
Now, (1)
不再是匹配的可行候选者,因此(2)显然是首选。
重做重载,以便首选 Base
取出小费谢奥的书,我们可以这样重构重载:
template <int I> struct choice : choice<I + 1> { };
template <> struct choice<10> { };
struct otherwise { otherwise(...) {} };
template <class T> void foo(T const& val) { foo_impl(val, choice<0>{}); }
现在我们可以更喜欢那些派生自的类型Base
:
template <class T, class = std::enable_if_t<std::is_convertible<T*, Base*>::value>>
void foo_impl(T const& val, choice<0> ); // new (2)
template <class T>
void foo_impl(T const& val, otherwise ); // new (1)
这改变了重载解析的工作机制,值得深入研究不同的情况。
Calling foo(d)
意味着我们正在打电话foo_impl(d, choice<0> )
并为我们提供了两个可行的候选者:
-
foo_impl(Derived const&, choice<0> )
, with [T = Derived]
-
foo_impl(Derived const&, otherwise )
, with [T = Derived]
在这里,第一个参数是相同的,但对于第二个参数,第一个重载是精确匹配,而第二个参数需要通过可变参数进行转换。otherwise
结果将始终是单词选择,因此首选第一个重载。正是我们想要的。
Calling foo(not_a_base)
另一方面,只会给我们one可行的候选人:
-
foo_impl(NotABase const&, otherwise )
, with [T = NotABase]
另一张则因推演失败而被删除。所以这无疑是最好的可行候选者,而且我们再次得到了我们想要的东西。
对于你的具体问题,我会写一些类似的内容:
template <class T>
size_t n_items(T const& cont) { return n_items(cont, choice<0>{}); }
with:
// has count?
template <class T>
auto n_items(T const& cont, choice<0> ) -> decltype(cont.count()) {
return cont.count();
}
// else, has size?
template <class T>
auto n_items(T const& cont, choice<1> ) -> decltype(cont.size()) {
return cont.size();
}
// else, use static_size
template <class T>
size_t n_items(T const& cont, otherwise )
return static_size(cont);
}