提问人:Łukasz Wojakowski 提问时间:12/27/2015 最后编辑:TemplateRexŁukasz Wojakowski 更新时间:12/30/2015 访问量:1073
如何让 ADL 更喜欢一个函数模板而不是另一个函数模板
How to have ADL prefer a function template to another
问:
我想知道是否可以让 ADL 选择在其中一个参数的类的命名空间(或其他一些定义明确的地方)中定义的函数模板,在其他函数模板可见的情况下。下面我举了一个鼓舞人心的例子,虽然我知道那个特定情况的解决方法(我在下面讨论),但总的来说,这个问题似乎是有道理的。
我认为避免使用友元声明而是将工作委托给方法有点酷,因此想出了
namespace n
{
struct a
{
auto swap(a& a2) -> void;
};
auto swap(a& a1, a& a2) -> void
{
a1.swap(a2);
}
}
auto main(void) -> int
{
n::a a1, a2;
using std::swap;
swap(a1,a2); // use case 1
n::swap(a1,a2); // use case 2
}
到目前为止,一切都很好,两个用例都运行良好,但是后来,我添加了第二个类,它有自己的交换方法,并决定通过将独立交换转换为模板来节省样板:
namespace n
{
struct a
{
auto swap(a& a2) -> void;
};
struct b
{
auto swap(b& b2) -> void;
};
template<class T>
auto swap(T& t1, T& t2) -> void
{
t1.swap(t2);
}
}
auto main(void) -> int
{
n::a a1, a2;
using std::swap;
swap(a1,a2); // use case 1
n::swap(a1,a2); // use case 2
}
在这里,用例 1 中断了,编译器抱怨模板的歧义。如果预料到这个问题,可以定义函数 rahter 而不是方法(它们通常是朋友,因为它们取代了方法):std::swap
swap
namespace n
{
struct a
{
friend auto swap(a& a1, a& a2) -> void;
};
struct b
{
friend auto swap(b& b1, b& b2) -> void;
};
}
现在一切正常,所以在这种情况下,记住使用友元函数比方法更合适,但是一般情况如何?是否有任何黑客,无论多么肮脏,都可以让编译器在全局命名空间中或由于某些子句可见的情况下明确选择(或我们控制下的其他一些),特别是如果后者不是我们要修改的?swap
n::foo<a>
foo<a>
template<class T> foo
using
答:
这里的罪魁祸首不仅仅是你写了,而是从根本上说,你提供了你自己的不受限制的函数模板交换
,每当在名称查找期间考虑命名空间 std
时,都会给出 std::swap
的重载解析错误(通过显式指令,或通过 ADL)。using std::swap
using
举例说明:在这种情况下,只要省略遗嘱即可拯救您using std::swap
auto main() -> int
{
n::a a1, a2;
swap(a1,a2); // use case 1
n::swap(a1,a2); // use case 2
}
但是,假设您将类重构为类模板和 ,并使用 (例如 ) 的模板参数调用它们,那么您会得到一个重载解析错误:a
b
b<T>
b<T>
namespace std
std::string
#include <iostream>
#include <string>
namespace n
{
template<class>
struct a /* as before */;
template<class>
struct b /* as before */;
}
auto main() -> int
{
n::a<std::string> a1, a2; // oops, ADL will look into namespace std
swap(a1,a2); // use case 1 (ERROR)
n::swap(a1,a2); // use case 2 (OK)
}
结论:如果使用相同的签名定义自己的版本(就重载解决而言),请始终限定对它的调用以禁用 ADL。swap
std::swap
提示:更好的是,不要偷懒,只需为自己的命名空间中的每个类提供自己的函数(而不是函数模板)。swap
另请参阅此 Q&A,其中解释了类似的机制,说明为什么提供自己的模板并期望它们与 ADL 一起使用是一个坏主意。begin
end
评论
swap
swap
swap(a, a)
swap(b,b)
namespace n
PP_PROVIDE_SWAP(a, b, ...)
std::is_same_v<T, a>
enable_if_t
swap
<utility>
swap
swap
swap
我知道我一定看起来很傻才能回答我自己的问题,但发布它的事实和讨论确实给我带来了一些新的理解。
回想起来,首先应该打动我的是序列
using std::swap;
swap(a1,a2);
它太老套了,显然一定是错误的,因为反复使用它需要复制粘贴算法(使用然后交换)。而且你不应该复制粘贴,即使算法是双行的。那么,还有什么可以做得更好的呢?把它变成一句话怎么样:using
stdfallback::do_swap(a1,a2);
让我提供允许这样做的代码:
namespace stdfallback
{
template<class T>
auto lvalue(void) -> typename std::add_lvalue_reference<T>::type;
template <typename T>
struct has_custom_swap
{
template<class Tp>
using swap_res = decltype(swap(lvalue<Tp>(),lvalue<Tp>()));
template <typename Tp>
static std::true_type test(swap_res<Tp> *);
template <typename Tp>
static std::false_type test(...);
static const bool value = decltype(test<T>(nullptr))::value;
};
template<class T>
auto do_swap(T& t1, T& t2) -> typename std::enable_if<has_custom_swap<T>::value,void>::type
{
swap(t1,t2);
}
template<class T>
auto do_swap(T& t1, T& t2) -> typename std::enable_if<!has_custom_swap<T>::value,void>::type
{
std::swap(t1,t2);
}
}
在解决方案中,您可以找到一个基于 SFINAE 的特征类,该类是 true 还是 false,具体取决于是否找到了用于交换实例化类型的左值的非限定调用(对于需要模板,类似于但解析为 l 值而不是 r 值),然后是存在自定义交换时方法的两个重载, 而当它不是。它们必须被调用 不同于 ,否则调用非限定自定义交换的那个不会编译,因为它本身对它尝试调用的交换不明确。has_custom_swap
value
lvalue
declval
do_swap
swap
所以也许我们应该考虑使用这种模式而不是既定的?using
(为了给予适当的信任,特征解决方案的灵感来自 http://blog.quasardb.net/sfinae-hell-detecting-template-methods/)
评论
a
b
has_custom_swap
namespace std
std::swap
std
static_assert
using
评论
swap
T&
identity_t<T>
template<class T> struct identity { typedef T type; }; template<class T> using identity_t = typename identity<T>::type;
template<class Type> auto swap(Type& t1, identity_t<Type>& t2) -> void
std::swap
s::swap
using std::swap
using