如何在构造函数中创建一个可以接受满足 std::forward_iterator<T> 的任何类型的模板类?

How do I create a template class that can accept any type that satisfies std::forward_iterator<T> in the constructor?

提问人:Matt 提问时间:7/18/2022 更新时间:7/22/2022 访问量:186

问:

我正在制作一个循环迭代器类,它的行为类似于前向迭代器,只是它在到达范围的末尾后循环回到起点。

template <typename T>
struct CircularIterator
{
    CircularIterator(T* begin, T* end);

    // Omitted overloaded operators

    T* mBegin;  // Points to beginning of range
    T* mIter;   // Current item
    T* mEnd;    // Points to end of range
};

没有从 STL 迭代器(例如)到原始指针 () 的转换。以下代码编译时出现错误:std::vector<int>::iterator>T*

std::vector<int> vec{1, 2, 3};
CircularIterator<int> iter(vec.begin(), vec.end());
error: cannot convert ‘__gnu_cxx::__normal_iterator<int*, std::vector<int> >’ to ‘int*’ in initialization

如何在构造函数中创建一个可以接受满足 std::forward_iterator<T> 的任何类型的模板类?我想避免为所使用的每个迭代器类型创建新的模板实例(例如,new for 和 .)CircularIteratorstd::array<T>::iteratorstd::deque<T>::iterator

任何建议都将被采纳。我绝对处于我的模板/概念知识的尽头,并期待任何资源来了解更多信息。谢谢。

C++ 模板 STL

评论

0赞 Matt 7/19/2022
一个可以实现我目标的替代构造函数:它初始化 、 ,并使用 STL 函数获取原始指针。template <typename Range> explicit CircularIterator(Range& range);mBeginmItermEnddata()
0赞 Caleth 7/19/2022
其中有 3 个容器具有成员函数:、、(加上非容器)。要么不适用于大多数容器,要么必须将迭代器类型作为模板的一部分。stddata()std::vectorstd::arraystd::basic_stringstd::string_viewCircularIterator

答:

3赞 康桓瑋 7/19/2022 #1

您可以约束必须满足 std::forward_iterator 的模板参数,例如ICircularIterator

#include <iterator>

template <std::forward_iterator I>
struct CircularIterator
{
    CircularIterator() = default;
    CircularIterator(I begin, I end);

    // Omitted overloaded operators

    I mBegin;  // Points to beginning of range
    I mIter;   // Current item
    I mEnd;    // Points to end of range
};

请注意,您需要提供默认构造函数,因为需要默认构造函数。CircularIteratorstd::forward_iterator

评论

0赞 Matt 7/19/2022
我为一个具有 256K 内存的嵌入式项目进行开发,因此我无法使用此解决方案,因为它会为每种类型的迭代器生成新的模板实例化。据我了解,由向量构造的 CircularIterator 和由数组构造的 CircularIterator 将是导致代码膨胀的不同类。
0赞 Caleth 7/19/2022
@Matt,如果你不能为每个迭代器类型使用不同的类型,你就不能是通用的
2赞 Ted Lyngmo 7/19/2022 #2

前向迭代器不必是连续范围的迭代器,因此不合适。您可以改为存储迭代器本身。以下是将 SFINAE 与 和 一起使用的一种可能方法:T* mBeginis_base_of_vforward_iterator_tag

#include <iterator>
#include <type_traits>

template <class It, class EndIt,
          std::enable_if_t<std::is_base_of_v<
              std::forward_iterator_tag,
              typename std::iterator_traits<It>::iterator_category>,int> = 0>
struct CircularIterator {
    CircularIterator(It begin, EndIt end)
        : mBegin(begin), mIter(begin), mEnd(end) {}

    It mBegin;   // Points to beginning of range
    It mIter;    // Current item
    EndIt mEnd;  // Points to end of range
};

如果你真的想存储以获得更少的类模板实例化,你必须要求迭代器改为:T*contiguous_iterator

#include <concepts>
#include <iterator>
#include <type_traits>

template <class T>
struct CircularIterator {
    CircularIterator(std::contiguous_iterator auto begin,
                     std::contiguous_iterator auto end)
        : mBegin(begin != end ? &*begin : nullptr),
          mIter(mBegin),
          mEnd(std::next(mBegin, std::distance(begin, end))) {}

    T* mBegin;  // Points to beginning of range
    T* mIter;   // Current item
    T* mEnd;    // Points to end of range
};

// deduction guide:
template<class It, class EndIt>
CircularIterator(It, EndIt) ->
        CircularIterator<typename std::iterator_traits<It>::value_type>;

...并像您的示例中一样使用:

#include <vector>

int main() {
    std::vector<int> vec{1, 2, 3};

    // specifying `<int>` is optional with the added deduction guide:
    CircularIterator iter(vec.begin(), vec.end());
}

演示

评论

0赞 Matt 7/19/2022
我认为我不能使用这个解决方案,因为它会为每种迭代器创建一个新的模板实例化。此代码适用于嵌入式项目。
0赞 Ted Lyngmo 7/19/2022
@Matt 它将为所有 s 创建一个实例。如果你有一个偶数,就会有所不同,整个模板实例化将加倍。相比之下,此迭代器模板的实例不会很大。vector<int>vector<double>vector<double>::iteratorvector<T>
0赞 Ted Lyngmo 7/19/2022
@Matt......但添加了一个可能需要较少实例化的选项
0赞 Caleth 7/19/2022
&*end是 UB,就像空容器一样&*begin
1赞 Caleth 7/19/2022
它绝对取消引用名为 的迭代器。尝试使用 msvc 的调试模式迭代器end
1赞 Caleth 7/19/2022 #3

你的欲望是直接竞争的

在构造函数中满足 std::forward_iterator 的任何类型?

避免为所使用的每个迭代器类型创建新的模板实例

选择其中之一。和 之间没有共同的类型作为 的数据成员。std::vector<int>::iteratorstd::deque<int>::iteratorCircularIterator<int>

在内存受限的环境中,你为什么要混合,而且首先?这些是更大的模板。如果您使用的唯一容器是 ,那么您持有 s 还是 in 有什么关系?std::arraystd::vectorstd::dequestd::vectorstd::vector<int>::iteratorint *CircularIterator<int>

评论

0赞 Matt 7/20/2022
为了回答您关于容器的无关问题,我使用的是嵌入式安全的专用容器。但是,单元测试可以使用std::vectorstd::deque
0赞 Caleth 7/20/2022
@Matt这并非毫无关系。如果您担心 ~20 行模板的少数实例的代码大小,为什么首先要有多个容器类型?