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