如何让 ADL 更喜欢一个函数模板而不是另一个函数模板

How to have ADL prefer a function template to another

提问人:Łukasz Wojakowski 提问时间:12/27/2015 最后编辑:TemplateRexŁukasz Wojakowski 更新时间:12/30/2015 访问量:1073

问:

我想知道是否可以让 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::swapswap

namespace n
  {
  struct a
    {
    friend auto swap(a& a1, a& a2) -> void;
    };
  struct b
    {
    friend auto swap(b& b1, b& b2) -> void;
    };
  }

现在一切正常,所以在这种情况下,记住使用友元函数比方法更合适,但是一般情况如何?是否有任何黑客,无论多么肮脏,都可以让编译器在全局命名空间中或由于某些子句可见的情况下明确选择(或我们控制下的其他一些),特别是如果后者不是我们要修改的?swapn::foo<a>foo<a>template<class T> foousing

C++ 模板 交换 using-directives argument-dependent-lookup

评论

0赞 Jarod42 12/28/2015
一个技巧是使第二个不可扣除(如类似),因此命名空间中的模板将比标准模板更专业......swapT&identity_t<T>
0赞 Łukasz Wojakowski 12/28/2015
我喜欢这个想法,但它似乎行不通。对于建议和交换修改,是被选中的。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) -> voidstd::swap
0赞 Jarod42 12/28/2015
确实,不太专业...... :/s::swap
0赞 Andrzej 1/5/2016
你能进一步澄清一下你问题的核心吗?我可以看到这里提到了三个问题。1)为了任务的缘故,只需根据命名空间对重载进行排序。2)修复交换的特定问题。3)将友元函数转换为成员函数作为原则(并找到适应这一点的方法)。例如,在您的示例中,回避友元函数是唯一的驱动力吗?
0赞 Łukasz Wojakowski 1/6/2016
它作为一个交换情况出现,但鉴于外部代码中长期建立的方式,交换的解决方案是独立的(朋友)函数,我可以接受它,所以不是 2)。但是,在其他情况下,对外部命名空间模板进行回退可能很有用,如果您在命名空间中专长的内容本身是不受限制的模板,则使用失败。我一直在找那个,所以我想这是你的 1)。也许一个解决方案也会影响交换的建议。在某些方面,这有点像穷人的命名空间概念。我不明白你的 3),你是什么意思?using std::swapusing

答:

1赞 TemplateRex 12/28/2015 #1

这里的罪魁祸首不仅仅是你写了,而是从根本上说,你提供了你自己的不受限制的函数模板交换,每当在名称查找期间考虑命名空间 std 时,都会给出 std::swap 的重载解析错误(通过显式指令,或通过 ADL)。using std::swapusing

举例说明:在这种情况下,只要省略遗嘱即可拯救您using std::swap

Live On Coliru(科里鲁生活公寓)

auto main() -> int
{
    n::a a1, a2;
    swap(a1,a2);    // use case 1
    n::swap(a1,a2); // use case 2
}

但是,假设您将类重构为类模板和 ,并使用 (例如 ) 的模板参数调用它们,那么您会得到一个重载解析错误:abb<T>b<T>namespace stdstd::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)
}

结论:如果使用相同的签名定义自己的版本(就重载解决而言),请始终限定对它的调用以禁用 ADL。swapstd::swap

提示:更好的是,不要偷懒,只需为自己的命名空间中的每个类提供自己的函数(而不是函数模板)。swap

另请参阅此 Q&A,其中解释了类似的机制,说明为什么提供自己的模板并期望它们与 ADL 一起使用是一个坏主意。beginend

评论

0赞 Łukasz Wojakowski 12/29/2015
好吧,你很好地解释了导致问题的机制,完成了我的问题,谢谢。我不想这样做,因为害怕让我的问题太冗长。但我在这个问题上的重点不是如何写,而是用函数而不是方法来控制。这需要纪律,但很容易记住。真正的问题是,你能通过注入、从嵌套命名空间继承来欺骗你所描述的机制吗,随便你说出来,这样在编译时你就可以得到所需的行为。你有什么想法吗,或者这是不可能的?swap
0赞 TemplateRex 12/29/2015
@ŁukaszWojakowski我已经告诉过你了:不要做一个函数模板,只要为你的 .或者,如果你真的讨厌样板,只需编写一个可变参数宏来生成它们,就像 .swapswap(a, a)swap(b,b)namespace nPP_PROVIDE_SWAP(a, b, ...)
0赞 Łukasz Wojakowski 12/29/2015
你说我不应该使用模板,而我问如果我坚持保留它,是否有任何机制可以让我这样做。交换是微不足道的,同样,我不是在问交换,这只是一个例子。我做了 RTFM 的一份,我知道你说的是通常的方式(我已经在问题中提到过),但我不确定标准是否不允许一些晦涩难懂的聪明来源,这就是我要问的。宏,是的,这是最后的手段。想要更好。
0赞 TemplateRex 12/30/2015
@ŁukaszWojakowski 我尝试了很多东西,特别是表达式 sfinae on 等,但无论你把它放在哪里(默认模板或函数参数、返回类型),它总是包括/排除重载集,不影响重载集中的(相等)秩。所以,我不知道有什么方法可以实现你想要的,但没有正式的证据证明这是不可能的:)std::is_same_v<T, a>enable_if_t
0赞 TemplateRex 12/30/2015
@ŁukaszWojakowski经过进一步的标准审查:该标准在 中提供了一个通用模板,但为每个容器和实用程序类提供了单独的重载,有时委托给成员交换,有时委托给成员/成员交换。由此我得出结论,不可能提供第二个通用(无限制)模板来调用成员交换。我认为你必须写一个提案,让一般在成员函数上执行 SFINAE ,并在成员交换不存在时执行 SFINAE 的第二个重载。swap<utility>swapswapswap
0赞 Łukasz Wojakowski 12/30/2015 #2

我知道我一定看起来很傻才能回答我自己的问题,但发布它的事实和讨论确实给我带来了一些新的理解。

回想起来,首先应该打动我的是序列

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_swapvaluelvaluedeclvaldo_swapswap

所以也许我们应该考虑使用这种模式而不是既定的?using

(为了给予适当的信任,特征解决方案的灵感来自 http://blog.quasardb.net/sfinae-hell-detecting-template-methods/)

评论

0赞 TemplateRex 12/30/2015
您能否为两个用户定义的类和一个带有成员交换的类和一个没有成员交换的类显示一个工作示例?ab
0赞 TemplateRex 12/30/2015
这个例子不能真正起作用。首先,它并不真正起作用(见这里),即使它会,然后用一个带有模板参数的类模板来调用它,仍然会带来一般模板并导致歧义。has_custom_swapnamespace stdstd::swap
0赞 Łukasz Wojakowski 12/30/2015
我真的很喜欢我从与你的讨论中获得的动力,但我不明白什么对你不起作用(和反对票),看这里。我接受你的观点,即使用来自的类型实例化一个类模板,代码中有一个与之对应的注释,但使用旧样式在这里无济于事,对吧?而且我知道如何在这里建立 ADL 屏障,等待下一条评论。我还有一个问题,我可以在这里获得一些帮助。stdstatic_assertusing
0赞 Łukasz Wojakowski 12/30/2015
请记住,我是黑客攻击的,我从来没有说过这应该进入生产阶段。当您实例化带有类的模板时,adl 会启动这些类的命名空间及其关联命名空间。但是,当您使用模板模板实例化模板时(哦,我的天!),只考虑这些模板模板的命名空间,而不考虑它们的关联。所以你需要一个包装器,看这里
0赞 Łukasz Wojakowski 12/30/2015
好的,很抱歉发送垃圾邮件,但我想我明白你的问题了:你是在问我的设备是否能够区分我的命名空间中具有方法交换的类和没有方法交换的类,因为所述命名空间中存在不受限制的交换模板?答案是否定的,这是正确的,如果一个人编写一个不受限制的模板,那么其意图是,与此模板一起使用的所有内容都应该符合给定的概念(或具有另一个重载),否则错误就是遗漏,并在使用时由编译器标记。