【问题标题】:How to implement std::distance() for custom templated iterator?如何为自定义模板化迭代器实现 std::distance()?
【发布时间】:2017-11-06 10:04:25
【问题描述】:

我有一个模板化的双向迭代器。我不想让它随机访问,因为it += n 操作不会是固定时间。但是,it2 - it1 操作恒定时间。我想为这个迭代器专门化std::distance(),以便使用它的算法(例如std::vector::assign())可以利用有效的差分操作。 如果迭代器是模板,我该怎么做?

这是一个玩具示例:

#include <iterator>
#include <iostream>

// template bidirectional iterator
template<typename T>
class iter : public std::iterator<std::bidirectional_iterator_tag, T> {
    T *ptr;
public:
    iter(T *ptr) : ptr(ptr) { }

    iter() = default;
    iter(const iter &) = default;
    iter &operator = (const iter &) = default;

    T *operator * () { return ptr; }

    bool operator == (const iter &it) { return ptr == it.ptr; }
    bool operator != (const iter &it) { return ptr != it.ptr; }

    iter &operator ++ () { ++ptr; return *this; }
    iter operator ++ (int) { iter tmp(*this); operator++(); return tmp; }

    iter &operator -- () { --ptr; return *this; }
    iter operator -- (int) { iter tmp(*this); operator--(); return tmp; }

    // Would not be used for a bidirectional iterator.
    // Implemented only so we can use it in std::distance() below.
    ptrdiff_t operator - (const iter &it) { return ptr - it.ptr; }
};

namespace std {
    // We could specialize std::distance() for iter<int> like this:
    template<>
    iter<int>::difference_type distance(iter<int> first, iter<int> last) {
        std::cout << "my distance called\n";
        return last - first;
    }

    // QUESTION: Can we do it in general, for iter<T> ?
}

// Just to test that everything works as intended.
int main() {
    int arr[5];
    iter<int> it1(&arr[0]);
    iter<int> it2(&arr[5]);

    std::cout << std::distance(it1, it2) << std::endl;

    return 0;
}

这是Is it reasonable to overload std functions such as std::distance?的后续

原则上我们可以这样做:

namespace std {
    template<class T>
    typename iter<T>::difference_type distance(iter<T> first, iter<T> last) {
        std::cout << "my distance called\n";
        return last - first;
    }
}

但这将是std::distance() 的重载,根据标准,std 命名空间函数不允许这样做。

【问题讨论】:

  • 啊,不能部分特化模板成员函数。并且不能超载它,就像你之前问的那样。难点。
  • 为什么不将 distance 函数与模板化迭代器放在同一个命名空间中,并通过对 distance 的非限定调用来依赖 ADL?
  • @MassimilianoJanes - 你的意思是标准算法将重新创建std::distance 的逻辑而不是使用它?当然,标准的干字允许这样做,但真的吗?
  • @Szabolcs:这仍然是个好主意。你可能对“不被自己以外的人使用”有点悲观。您面临的问题(std:: 中没有重载)是众所周知的,标准解决方案是使用using std::foo; foo(arg1, arg2)。首选 ADL,回退到 std::foo
  • 根据 [global.functions]/4,除非另有说明,否则标准库不会对非运算符函数进行启用 ADL 的调用。

标签: c++ templates template-specialization


【解决方案1】:

正确的做法是将distance 方法定义在与iter-模板相同的命名空间中(在本例中为全局命名空间)。

....
    typename iter::difference_type operator -(const iter &it)
    {
        return ptr - it.ptr;
    }
}; // close template<typename T> class iter

template<typename T>
typename iter<T>::difference_type distance( iter<T> first,  iter<T> last)
{
    std::cout << "my distance called\n";
    return last - first;
}

稍后使用 ADL,如本例所示:

int main()
{
    int arr[5];
    iter<int> it1(&arr[0]);
    iter<int> it2(&arr[5]);

    using std::distance;
    using std::begin;
    using std::end;

    std::cout << distance(it1, it2) << '\n';
    std::cout << "using std::distance\n";
    std::cout << distance(begin(arr), end(arr)) << '\n';

    return 0;
}

将输出:

my distance called
5
using std::distance
5

Scott Meyers 在他的书“Effective C++”,第三版,第 25 项中给出了对 std 方法模板的部分特化问题的一个很好的解释。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-18
    • 2018-04-27
    • 1970-01-01
    • 2018-08-25
    • 1970-01-01
    • 2015-11-01
    相关资源
    最近更新 更多