代码之家  ›  专栏  ›  技术社区  ›  alfC

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

  •  16
  • alfC  · 技术社区  · 7 年前

    在泛型函数中,我使用以下习惯用法:,

    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);
    }
    

    do_something 是一个泛型函数,它不应该知道任何其他库的特定信息(也许除了 std:: ).

    N .

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

    我想重载此命名空间中这些迭代器的副本。 当然,我会:

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

    但是当我打电话的时候 具有 N::A , N::B N::C 参数我得到“模棱两可的复制调用”,即使它们与 N::copy .

    std::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;
        }
    }
    

    但这没用。

    对于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); 
    }
    

    典型的错误消息是

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


    7 回复  |  直到 7 年前
        1
  •  4
  •   alfC    7 年前

    你可以申报 copy() public friend function 在迭代器类中。 这在某种程度上可以替代部分专门化(这对于函数来说是不可能的),因此重载解析将首选它们,因为它们更专业:

    #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;
    }
    

    this demo 来验证它是否有效。

    我引入了一个公共基类,它为所有迭代器声明了必要的朋友。因此,与您尝试声明标记不同,您只需从 ItBase .

    N::copy() N ,可能不再需要它,因为这些好友函数将在中公开可见 N 无论如何(就好像它们是自由函数一样)。


    更新:

    N 不管怎样,只要声明一个公共基类 N::copy 使用这个基类,例如。

    namespace N
    {
        template <class SomeN2>
        SomeN2 copy(ItBase first, ItBase last, SomeN2 d_first) { ... }
    }
    

    std::copy 将永远被优先于 N::复制 因为如果你传递了一个 A ,它必须向下浇铸才能匹配 N::复制 而不需要铸造 Here 你可以很明显地看到这一点 复制 试图调用(这会导致错误,因为 N::A

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

    但是,请注意,如果您的迭代器碰巧有一些要在实现中使用的公共成员(是否派生自某个公共基类并不重要) N::复制 ,您可以使用我上面的解决方案,如下所示:

    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() {} };
    }
    

    here 它是如何工作的。


    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){
            ...
        }
    } 
    

    自今(非朋友) copy 复制 N 复制 函数是可选组件。

        2
  •  3
  •   Maxim Egorushkin    7 年前

    一种可能的解决方案是使用另一个函数模板名称和类型鉴别器,允许参数相关名称查找在参数的命名空间中查找关联函数:

    template<class T> struct Tag {};
    template<class T> Tag<void> tag(T const&);
    
    template<class It1, class It2>
    void mycopy(It1 first, It1 second, It2 d_first, Tag<void>) {
        std::cout << "std::copy\n";
    }
    
    template<class It1, class It2>
    void mycopy(It1 first, It1 second, It2 d_first) {
        mycopy(first, second, d_first, decltype(tag(first)){}); // Discriminate by the type of It1.
    }
    
    namespace N{
    
        struct itA{using trait = void;};
        Tag<itA> tag(itA);
    
        template<class It1, class It2>
        void mycopy(It1 first, It1 second, It2 d_first, Tag<itA>) {
            std::cout << "N::mycopy\n";
        }
    }
    
    int main() {
        char* p = 0;
        mycopy(p, p, p); // calls std::copy
    
        N::itA q;
        mycopy(q, q, q); // calls N::mycopy
    }
    
        3
  •  2
  •   geza    7 年前

    这似乎满足了您的要求:

    namespace SpecCopy {
    
    template <typename A, typename B, typename C>
    void copy(A &&a, B &&b, C &&c) {
        std::copy(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c));
    }
    
    }
    
    template<class It1, class It2>
    void do_something(It1 first, It1 second, It2 d_first){
        using namespace SpecCopy;
        copy(first, second, d_first);
    }
    

    基本上,这取决于日常生活能力。如果ADL未找到任何函数,则将使用 SpecCopy::copy ,这是对的包装 std::copy .


    因此,如果你这样做:

    N::A a1, a2, a3;
    do_something(a1, a2, a3);
    

    然后 do_something 将呼叫 N::copy .


    如果您这样做:

    std::vector<int> a1, a2;
    do_something(a1.begin(), a1.end(), a2.begin());
    

    然后 做点什么 将呼叫 复制 ,它将调用 .


    int *a1, *a2, *a3;
    do_something(a1, a2, a3);
    

    做点什么 将呼叫 复制 ,它将调用 复制 .

        4
  •  2
  •   Timo    7 年前

    在C++ 11中,可以使用标签调度。如果您可以对自定义迭代器进行一些更改,那么实现起来就会简单一些。

    #include <iostream>
    #include <algorithm>
    #include <vector>
    #include <type_traits>
    
    // indicates that the type doesn't have a tag type (like pointers and standard iterators)
    struct no_tag{};
    
    namespace detail 
    {
        template <typename T>
        auto tag_helper(int) -> typename T::tag;
    
        template <typename>
        auto tag_helper(long) -> no_tag;
    }
    
    // get T::tag or no_tag if T::tag isn't defined.
    template <typename T>
    using tag_t = decltype(detail::tag_helper<T>(0));
    
    namespace N
    {
        struct my_iterator_tag {};
        struct A{ using tag = my_iterator_tag; };
        struct B{ using tag = my_iterator_tag; };
        struct C{ using tag = my_iterator_tag; };
    }
    
    namespace N
    {
        template<class SomeN1, class SomeN2>
        SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, no_tag)
        {
            std::cout << "calling std::copy\n";
            return std::copy(std::forward<SomeN1>(first), std::forward<SomeN1>(last), std::forward<SomeN2>(d_first));
        }
    
        template<class SomeN1, class SomeN2>
        SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, my_iterator_tag)
        {
            // your custom copy        
            std::cout << "custom copy function\n";
            return {};
        }
    
        template<class SomeN1, class SomeN2>
        SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first)
        {
            return copy_helper(std::forward<SomeN1>(first), std::forward<SomeN1>(last), std::forward<SomeN2>(d_first), tag_t<SomeN1>{});
        }
    }
    
    template<class It1, class It2>
    void do_something(It1 first, It1 second, It2 d_first)
    {
        N::copy(first, second, d_first);
    }
    
    int main()
    {
        N::A a1, a2, a3;
        std::cout << "using custom iterator: ";
        do_something(a1, a2, a3); 
    
        std::cout << "using vector iterator: ";
        std::vector<int> v;
        do_something(std::begin(v), std::end(v), std::begin(v));
    
        std::cout << "using pointer: ";
        int* ptr = new int[10];
        do_something(ptr, ptr + 5, ptr);
    
        return 0;
    }
    

    tag iterator_category ). 标签 可以是您想要的任何类型,它只需与您在中用作标记的类型匹配即可 copy_helper

    接下来,我们定义一个类型,允许我们访问这个 类型,或在以下情况下返回到默认类型 标签 不存在。这将帮助我们区分自定义迭代器和标准迭代器和指针。我使用的默认类型是 no_tag tag_t 通过使用SFINAE和重载解析为我们提供此功能。我们称之为函数 tag_helper(0) 它有两个声明。第一个回来了 T::tag 而第二个回来了 无标签 . 使命感 标记辅助对象(0) int 是一个更好的匹配 0 long . 这意味着我们将始终尝试访问 T::标记 T::标记 tag_helper(int) 挑选 tag_helper(long) .

    最后,我们只需要为每个标记实现一个复制函数(我调用了它) )和另一个拷贝功能,作为方便的环绕(我使用 N::copy

    Here 这是一个活生生的例子。

    如果将代码移动一点,就可以断开名称空间 N 并依靠日常生活能力:

    #include <iostream>
    #include <algorithm>
    #include <vector>
    #include <type_traits>
    
    // indicates that the type doesn't have a tag type (like pointers and standard iterators)
    struct no_tag{};
    
    namespace detail 
    {
        template <typename T>
        auto tag_helper(int) -> typename T::tag;
    
        template <typename>
        auto tag_helper(long) -> no_tag;
    }
    
    // get T::tag or no_tag if T::tag isn't defined.
    template <typename T>
    using tag_t = decltype(detail::tag_helper<T>(0));
    
    namespace N
    {
        struct my_iterator_tag {};
        struct A{ using tag = my_iterator_tag; };
        struct B{ using tag = my_iterator_tag; };
        struct C{ using tag = my_iterator_tag; };
    
        template<class SomeN1, class SomeN2>
        SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, my_iterator_tag)
        {
            // your custom copy        
            std::cout << "custom copy function\n";
            return {};
        }
    }
    
    template<class SomeN1, class SomeN2>
    SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, no_tag)
    {
        std::cout << "calling std::copy\n";
        return std::copy(std::forward<SomeN1>(first), std::forward<SomeN1>(last), std::forward<SomeN2>(d_first));
    }
    
    template<class It1, class It2>
    void do_something(It1 first, It1 second, It2 d_first)
    {
        copy_helper(std::forward<It1>(first), std::forward<It1>(second), std::forward<It2>(d_first), tag_t<It1>{});
    }
    
    int main()
    {
        N::A a1, a2, a3;
        std::cout << "using custom iterator: ";
        do_something(a1, a2, a3); 
    
        std::cout << "using vector iterator: ";
        std::vector<int> v;
        do_something(std::begin(v), std::end(v), std::begin(v));
    
        std::cout << "using pointer: ";
        int* ptr = new int[10];
        do_something(ptr, ptr + 5, ptr);
    
        return 0;
    }
    
        5
  •  1
  •   Matthieu Brucher    7 年前

    好的,在@paler123上构建,但不检查现有类型,但检查是否 It1 而是指针:

    namespace N{
      struct A{};
      struct B{};
      struct C{};
    }
    
    namespace N{
        template<class SomeN1, class SomeN2>
        SomeN2 copy(SomeN1, SomeN1, SomeN2 c){
            std::cout << "here" << std::endl;
            return c;
        }
    }
    template<class It1, class It2>
    void do_something(It1 first, It1 second, It2 d_first){
        if constexpr (std::is_pointer_v<It1>) {
            std::copy(first, second, d_first);
        }
        else
        {
            copy(first, second, d_first);
        }
    }
    
    
    int main(){
        N::A a1, a2, a3;
        do_something(a1, a2, a3); 
    
        int* b1, *b2, *b3;
    
        do_something(b1, b2, b3); 
    }
    

    std::copy 否则,我们依赖日常生活能力。

    一般来说,您的问题是一个设计问题。你想用 复制 对于所有情况,来自的对象除外 N 复制 ,则删除正确ADL的选项。你不能拥有一切,你必须重新设计你的代码。

        6
  •  1
  •   alfC    7 年前

    (这些注释现在集成在我对@sebrockm答案的编辑中)


    它不是很好,因为它需要把所有的东西都包起来 N:: 另一个模板类(称为 wrap do_something N 班级需要了解特殊课程 N::copy . 价格是 main 调用方必须显式地包装 类是丑陋的,但从耦合的角度来看,这是很好的,因为这是应该了解整个系统的唯一代码。

    #include <iostream>
    #include <algorithm>
    #include <vector>
    
    namespace N{
        struct A{};
        struct B{};
        struct C{};
    }
    
    namespace N{
    
        template<class S> struct wrap : S{};
    
        template<class SomeN1, class SomeN2>
        SomeN2 copy(wrap<SomeN1> first, wrap<SomeN1> last, wrap<SomeN2> d_first)
        {
            std::cout << "here" << std::endl;
            return d_first;
        }
    }
    
    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::wrap<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;
    }
    
        7
  •  -2
  •   alfC    7 年前

    建议您看一看功能强大的新款 Boost.HOF

    此函数完全满足您的要求:

    #include <boost/hof.hpp>
    
    template<class It1, class It2>
    void do_something(It1 first, It1 second, It2 d_first){
        namespace hof = boost::hof;
    
        auto my_copy = hof::first_of(
        [](auto first, auto second, auto d_first) -> decltype(N::copy(first, second, d_first))
        {
            return N::copy(first, second, d_first);
        },
        [](auto first, auto second, auto d_first) -> decltype(std::copy(first, second, d_first))
        {
            return std::copy(first, second, d_first);
        });
        my_copy(first, second, d_first);
    }
    

    hof::first_of 将选择返回类型推断为合法表达式的结果类型的第一个lambda。

    推荐文章