我想知道在其他函数模板可见的情况下是否可以让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中断,编译器抱怨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
子句,特别是如果后者不是我们要修改的那个?
答案 0 :(得分:0)
这里的罪魁祸首不仅仅在于您编写using std::swap
,而且从根本上说您提供了自己的无限制功能模板swap
,这将导致std::swap
出现重载解析错误在名称查找期间考虑namespace std
(通过显式using
指令或ADL)。
举例说明:在这种情况下,只需省略using std::swap
即可拯救你
<强> Walker::start_lvl 强>
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
),然后你得到一个重载决议错误:
<强> Live On Coliru 强>
#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)
}
结论:如果您使用与swap
相同的签名定义自己的std::swap
版本(就重载解析而言),请始终限定对其的调用为了禁用ADL。
提示:更好的是,不要懒惰,只为自己的命名空间中的每个类提供自己的swap
函数(不是函数模板)。
另请参阅Live On Coliru,其中解释了为什么提供您自己的begin
和end
模板以及期望它们与ADL一起使用是一个坏主意的原因。
答案 1 :(得分:-1)
我知道回答我自己的问题我一定很傻,但发布它和讨论的事实确实给我带来了一些新的理解。
在回顾中,首先应该让我印象深刻的是序列
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的特征类has_custom_swap
,其value
为真或假,具体取决于是否找到了对实例化类型的左值进行交换的非限定调用(对于此需要lvalue
模板,类似于declval
但是解析为l值而不是r值),然后是do_swap
方法的两次重载,用于存在自定义交换时的情况,以及什么时候不是。它们必须被称为与swap
不同,否则调用非限定自定义交换的那个不会编译,因为它本身对它试图调用的交换不明确。
所以也许我们应该考虑使用这种模式而不是已建立的using
?
(为了给予适当的信任,特质解决方案的灵感来自http://blog.quasardb.net/sfinae-hell-detecting-template-methods/)