如何编写具有高过载优先级的类似标准的函数

alf*_*lfC 17 c++ ambiguous argument-dependent-lookup c++11

在通用函数中,我使用以下习语,

template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
    ... other stuff here...
    using std::copy;
    copy(first, second, d_first);
}
Run Code Online (Sandbox Code Playgroud)

do_something是一个通用函数,不应该知道任何其他任何库(可能除外std::).

现在假设我的命名空间中有几个迭代器N.

namespace N{

  struct itA{using trait = void;};
  struct itB{using trait = void;};
  struct itC{using trait = void;};

}
Run Code Online (Sandbox Code Playgroud)

我想在这个命名空间中重载这些迭代器的副本.我当然会这样做:

namespace N{
    template<class SomeN1, class SomeN2>
    SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first){
        std::cout << "here" << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,当我打电话do_somethingN::A,N::BN::C争论,我得到"暧昧通话复制"即使这些都在同一个命名空间N::copy.

有没有办法std::copy在上述原始功能的背景下赢得胜利?

我虽然如果我把约束放在模板参数上,那么N::copy将是首选.

namespace N{
    template<class SomeN1, class SomeN2, typename = typename SomeN1::trait>
    SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first){
        std::cout << "here" << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

但它没有帮助.

我可以尝试将通用调用复制到参数命名空间中的副本而不是其他变通方法std::copy.

完整代码:

#include<iostream>
#include<algorithm>
namespace N{
  struct A{};
  struct B{};
  struct C{};
}

namespace N{
    template<class SomeN1, class SomeN2>
    SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first){
        std::cout << "here" << std::endl;
    }
}

template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
    using std::copy;
    copy(first, second, d_first); // ambiguous call when It is from namespace N (both `std::copy` and `N::copy` could work.
}

int main(){
    N::A a1, a2, a3;
    do_something(a1, a2, a3); 
}
Run Code Online (Sandbox Code Playgroud)

典型的错误消息是

error: call of overloaded ‘copy(N::A&, N::A&, N::A&)’ is ambiguous


我是否认为C++ Concepts在这方面有所帮助,因为它更喜欢具有更多约束而不是更少约束的函数调用?

seb*_*ckm 4

您可以在迭代器类中声明copy()公共友元函数。这有点像部分专业化的替代(这对于函数来说是不可能的),因此它们将被重载决议首选,因为它们更专业:

#include <iostream>
#include <algorithm>
#include <vector>

namespace N
{
    template<class SomeN1, class SomeN2>
    SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first)
    {
        std::cout << "here" << std::endl;
        return d_first;
    }

    template <class T>
    struct ItBase
    {
        template <class SomeN2>
        friend SomeN2 copy(T first, T last, SomeN2 d_first)
        {
            return N::copy(first, last, d_first);
        }
    };

    struct A : ItBase<A>{};
    struct B : ItBase<B>{};
    struct C : ItBase<C>{};
}

template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
    using std::copy;
    copy(first, second, d_first);
}

int main(){
    N::A a1, a2, a3;
    std::cout << "do something in N:" << std::endl;
    do_something(a1, a2, a3); 

    std::vector<int> v = {1,2,3};
    std::vector<int> v2(3);
    std::cout << "do something in std:" << std::endl;
    do_something(std::begin(v), std::end(v), std::begin(v2));
    for (int i : v2)
        std::cout << i;
    std::cout << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

请参阅此演示以验证其是否有效。

我引入了一个公共基类,它为所有迭代器声明了必要的朋友。因此,您不必像您尝试的那样声明标签,而只需继承自ItBase.

注意:如果N::copy()应该仅使用 中的这些迭代器N,则可能不再需要它,因为这些友元函数无论如何都会公开可见N(就好像它们是自由函数一样)。


更新:

在评论中,有人建议,如果迭代器N无论如何都有一个公共基类,则只需N::copy使用该基类进行声明,例如

namespace N
{
    template <class SomeN2>
    SomeN2 copy(ItBase first, ItBase last, SomeN2 d_first) { ... }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,这会产生与所需效果相反的效果:std::copy将始终优先于N::copy因为如果传递 的实例A,则必须将其向下转换才能匹配N::copy,而 则不需要强制转换std::copy在这里你可以看到显然是试图被调用(这会因为缺少一些 typedef 而std::copy给出错误)。N::A

因此,您不能利用公共基类来签名N::copy. 我在解决方案中使用它的唯一原因是避免重复代码(必须在每个迭代器类中声明友元函数)。我的ItBase根本不参与重载决策。

但是请注意,如果您的迭代器碰巧有一些您想要在 的实现中使用的通用成员(无论是否从某个通用基类派生并不重要)N::copy,您可以使用我上面的解决方案来执行此操作,如下所示:

namespace N
{
    template <class T>
    struct ItBase
    {
        template <class SomeN2>
        friend SomeN2 copy(T first, T last, SomeN2 d_first)
        {
            first.some_member();
            last.some_member();
            return d_first;
        }
    };

    struct A : ItBase<A>{ void some_member() {} };
    struct B : ItBase<B>{ void some_member() {} };
    struct C : ItBase<C>{ void some_member() {} };
}
Run Code Online (Sandbox Code Playgroud)

看看这里它是如何工作的。


同样,如果 A、B、C 具有共同的行为,那么可以用以某种方式参数化的共同模板类来替换它们。

namespace N
{
    template <class T, int I>
    struct ItCommon
    {
       ...
    };
    using A = ItCommon<double,2>;
    using B = ItCommon<int, 3>;
    using C = ItCommon<char, 5>;
}
...
namespace N{
    template<class T, int I, class Other>
    SomeN2 copy(ItCommon<T, I> first, ItCommon<T, I> last, Other){
        ...
    }
} 
Run Code Online (Sandbox Code Playgroud)

由于这个(非友元)copy函数肯定比std::copyand 由于 ADL 受到更多限制,因此当参数之一属于N命名空间时,它将具有高优先级。另外,作为非好友,此copy功能是可选组件。