如何让ADL更喜欢功能模板到另一个

时间:2015-12-27 15:43:31

标签: c++ templates swap using-directives argument-dependent-lookup

我想知道在其他函数模板可见的情况下是否可以让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子句,特别是如果后者不是我们要修改的那个?

2 个答案:

答案 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
}

但是假设您将类ab重构为类模板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,其中解释了为什么提供您自己的beginend模板以及期望它们与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/