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

问题描述 投票:17回答: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;
    }
}

然而,当我打电话do_somethingN::AN::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;
    }
}

但它并不能帮助。

我可以尝试哪些其他的解决方法一般呼叫复制到喜欢副本中的参数,而不是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); 
}

典型的错误消息是

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


难道我真的认为C ++概念将有助于通过这里与宁愿小于约束的更多约束上的函数调用?

c++ c++11 ambiguous argument-dependent-lookup
7个回答
4
投票

你可以声明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::copy首选,因为如果你传递A的一个实例,它会同时需要N::copy没有投以匹配std::copy被downcasted。 Here你可以看到明显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() {} };
}

here它是如何工作的。


在同一行,如果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){
        ...
    }
} 

由于这个(非友)copy功能绝对比std::copy因为ADL的更多约束,就会产生时的一个参数属于N命名空间的高度重视。此外,作为一个非朋友,这copy功能是一个可选组件。


3
投票

一个可能的解决方案是使用另一个函数模板名称,然后鉴别允许参数相关的名称查找找到的参数的命名空间相关联的功能:

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
}

2
投票

这似乎满足您的要求:

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。如果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());

然后do_something将调用SpecCopy::copy,它将调用std::copy


如果你这样做:

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

于是同样的事情发生以前一样:do_something将调用SpecCopy::copy,它将调用std::copy


2
投票

在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)。 tag可以是任何你想要的类型,它只是匹配你的标签在copy_helper使用类型。

接下来,我们定义了一个类型,它允许我们访问此tag类型,或将回落到默认类型,如果tag不存在。这将帮助我们我们自定义的迭代器和标准迭代器和指针区别开来。我使用的默认类型是no_tag。该tag_t为我们提供了通过使用SFINAE和重载此功能。我们调用函数tag_helper(0)有两个声明。而第二个返回T::tag第一个返回no_tag。调用tag_helper(0)将始终尝试使用的第一个版本,因为int0long更好的匹配。这意味着我们将始终尝试首先访问T::tag。然而,如果这是不可能的(未被定义T::tag)SFINAE踢和skipps tag_helper(int)选择tag_helper(long)

最后,我们只需要实现为每个标签的复印功能(我把它叫做copy_helper)和另一个副本功能周围包裹的舒适(我用N::copy)。该包装函数然后创建正确的代码类型,并调用相应的辅助功能。

Here是一个活生生的例子。

编辑

如果移动代码了一下周围,你可以断开的命名空间N并依靠ADL:

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

1
投票

OK,建立在@ 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); 
}

不过C ++ 17,但在三分球的情况下,我们通过明确std::copy否则,我们依靠ADL。

在一般情况下,你的问题是设计问题。你想用std::copy所有的情况下,除了从N对象,在这种情况下,你希望ADL会工作。但是当你被迫std::copy,删除正确ADL的选项。你不能拥有一切,你必须重新设计你的代码。


1
投票

(这些说明现在集成在我的编辑@ sebrockm的答案)


为了便于讨论,我会写一个答案,我自己的问题用另一种选择。

因为它需要包装在另一个模板类(称为N::这里)所有的wrap班,它是不是很漂亮。好事是do_something也不N类需要了解的特殊N::copy。价格是,main调用者有明确包裹N::类是丑陋的,但它是从视图耦合点很好,因为这是应该了解整个系统的唯一代码。

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

-2
投票

建议你看看非常强大的新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将选择第一拉姆达其返回类型推断为是合法表达式的结果类型。

© www.soinside.com 2019 - 2024. All rights reserved.