std::is_same 循环语句

std::is_same for a loop statement

我有两个结构,其方法在它们拥有的对象集合的开始和结束处返回迭代器。方法有不同的名称(这似乎是一个糟糕的应用架构,但这只是一个简化的模型):

struct A {
  std::vector<int>::iterator a_begin() { return v.begin(); }
  std::vector<int>::iterator a_end() { return v.end(); }

  std::vector<int> v = { 1, 2 };
};

struct B {
  std::vector<float>::iterator b_begin() { return v.begin(); }
  std::vector<float>::iterator b_end() { return v.end(); }

  std::vector<float> v = { 1.0f, 2.0f };
};

我想编写一个模板函数,它将遍历给定对象(类型 A 或类型 B)并对其元素做一些工作。我的做法是:

template<class T>
void foo(T t) {
  if constexpr (std::is_same_v<T, A>) {
    for (auto it = t.a_begin(); it != t.a_end(); it++) {
      // a lot of stuff
    }
  } else if constexpr (std::is_same_v<T, B>) {
    for (auto it = t.b_begin(); it != t.b_end(); it++) {
      // the same stuff
    }
  }
}

我觉得有点难看,因为 for 循环体是一样的。有什么办法可以改善吗?

迭代器的关键概念是两个迭代器定义一个序列。这就是它的全部内容:只需使用一对迭代器,而不是容器:

template <class It>
void foo(It begin, It end) {
    while (begin != end) {
        // a lot of stuff
        ++begin;
    }
}

现在您可以使用您喜欢的任何类型的容器定义的范围来调用它:

A a;
foo(a.a_begin(), a.a_end());

B b;
foo(b.b_begin(), b.b_end());

我从表面上接受你关于命名和复杂性的主张,如此抽象和桥接。

namespace detail {
    inline auto foo_begin(A& a) { return a.a_begin(); }
    inline auto foo_end  (A& a) { return a.a_end();   }
    inline auto foo_begin(B& b) { return b.b_begin(); }
    inline auto foo_end  (B& b) { return b.b_end();   }
}

template<class T>
void foo(T t) {
    for (auto it = detail::foo_begin(t); it != detail::foo_end(t); ++it) {
      // the same stuff
    }
}

您想要改变的操作是范围选择。因此,为您关心的类型设置一个小的重载集应该可以很好地完成它。

如果您经常这样做,范围适配器可能值得考虑。您可以手写,或者使用 C++20 的 std::ranges::subrange,您甚至可以利用这个重载集本身。

template<class T>
void foo(T t) {
    for (auto &item : std::ranges::subrange(detail::foo_begin(t), detail::foo_end(t))) {
      // the same stuff
    }
}

也许这有助于重新思考您的问题。

#include <vector>
#include <algorithm>

template<typename T>
void DoStuff(const T& value)
{
};

template<typename T>
void DoAllStuffFor(const std::vector<T>& v)
{
    std::for_each(v.begin(), v.end(), DoStuff<T>);
}

int main()
{
    std::vector<int> v1 = { 1, 2 };
    std::vector<double> v2 = { 1, 2 };

    DoAllStuffFor(v1);
    DoAllStuffFor(v2);
}