Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a standard-like function that has high overload priority

In a generic function I use the following idiom,

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 is a generic function that shouldn't know anything specific about any other libraries (except perhaps std::).

Now suppose I have several iterator in my namespace N.

namespace N{

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

}

An I want to overload copy for these iterators in this namespace. Naturally I would do:

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

However when I call do_something with N::A, N::B or N::C argument I get "ambiguous call to copy" even though these are in the same namespace as N::copy.

Is there a way to win over std::copy in the context of the original function above?

I though that if I put constrains over the template arguments then N::copy would be preferred.

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

but it doesn't help.

What other workarounds can I try for the generic call to copy to prefer to a copy in the namespace of arguments rather than std::copy.

Complete code:

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

A typical error message is

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


Am I right to think that C++ Concepts will help here by preferring function calls with more contraints than less constraints?

like image 546
alfC Avatar asked Jan 28 '19 11:01

alfC


2 Answers

You can declare copy() as a public friend function in your iterator classes. This works kind of as a replacement for partial specialization (which is impossible for functions), so that they will be preferred by overload resolution as they are more specialized:

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

See this demo to verify that it works.

I introduced a common base class that declares the necessary friends for all of your iterators. So, instead of declaring a tag, as you tried, you just have to inherit from ItBase.

Note: If N::copy() is supposed to work with only these iterators in N, it might not be needed anymore as these friend functions will be publicly visible in N anyway (as if they were free functions).


Update:

In the comments, it has been suggested, if the iterators in N have a common base class anyway, to just declare N::copy with this base class, e.g.

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

Unfortunately, this would have the opposite effect of the desired one: std::copy will always be preferred over N::copy because if you pass an instance of A, it would have to be downcasted in order to match N::copy while no cast is required for std::copy. Here you can see that obviously std::copy is tried to be called (which gives an error because N::A lacks some typedefs).

So, you cannot leverage a common base class for the signature of N::copy. The one and only reason I used one in my solution was to avoid duplicate code (having to declare the friend function in every iterator class). My ItBase does not participate in overload resolution at all.

Note, however, if your iterators happen to have some common members (whether derived from some common base class or not is not important) that you want to use in your implementation of N::copy, you can just do that with my solution above like so:

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

See here how it works.


On the same lines, if A, B, C have common behavior then it could be possible to replaced them by common template class parameterized in some way.

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

Since this (non-friend) copy function is definitely more constrained than the std::copy and because of ADL, it will have high priority when one of the arguments belongs to the N namespace. Also, being a non-friend, this copy function is an optional component.

like image 163
sebrockm Avatar answered Sep 26 '22 03:09

sebrockm


One possible solution is to use another function template name and type discriminators to allow argument-dependent name lookup to find the associated function in the namespace of the arguments:

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
}
like image 21
Maxim Egorushkin Avatar answered Sep 24 '22 03:09

Maxim Egorushkin