本文记录C++中如何使自定义对象支持for range和结构化绑定
实现一个支持基于范围的for循环的类
自定义类支持for range的条件是:
- 该类型需要有和其类型相关的
begin和end函数,可以是成员函数也可以是全局函数。 begin和end返回的对象需要支持operator*、operator!=和operator++运算符。
以下是一个demo:
#include <iostream>
class IntIter
{
public:
IntIter(int* p) : p_{p}
{
}
bool operator!=(const IntIter& other)
{
return p_ != other.p_;
}
const IntIter& operator++()
{
p_++;
return *this;
}
int operator*() const
{
return *p_;
}
private:
int* p_;
};
template <unsigned int fix_size>
class FixIntVector
{
public:
FixIntVector(std::initializer_list<int> init_list)
{
int* cur = data_;
for (auto e : init_list)
{
*cur = e;
cur++;
}
}
IntIter begin()
{
return IntIter{data_};
}
IntIter end()
{
return IntIter{data_ + fix_size};
}
private:
int data_[fix_size]{0};
};
int main()
{
FixIntVector<10> fix_int_vector{1, 3, 5, 7, 9};
for (auto e : fix_int_vector)
{
std::cout << e << std::endl;
}
}
编译运行:

实现了begin和end的好处远不止于for range,还可以使用符合条件的STL的函数。
比如std::for_each和std::reverse,std::for_each的使用demo:
int main()
{
FixIntVector<10> fix_int_vector{1, 3, 5, 7, 9};
std::for_each(fix_int_vector.begin(), fix_int_vector.end(), [&](int num) { std::cout << num << " "; });
std::cout << std::endl;
return 0;
}
执行结果:

但是上面实现的迭代器还不能使用std::sort,因为std::sort要求迭代器是双向随机迭代器,所以需要拓展满足双向随机迭代器的条件后才能使用。
日常开发中,并不总是需要为自定义类型添加扩展来使用STL的,大部分时候标准库自带的容器就够用了,不明白为啥老是要自己定义一套。能用标准库提供的就用标准库提供的,可以去这个网站先看看标准库有没有提供对应算法,没有再自己写,挺好用的

结构化绑定
C++17提供了结构化绑定的特性,可以将一个或多个名称绑定到初始化对象中的一个或者多个子对象上。
绑定到原生数组
结构化绑定数组的时候,编译器要能知道原生数组的元素个数

绑定到结构体和类对象
将标识符绑定到结构体和类的非静态成员变量上要满足以下条件:
- 类或者结构体中的非静态数据成员个数必须和标识符列表中的别名的个数相同
- 这些数据成员必须是公有的
- 数据成员必须是在同一个类或者基类中
- 绑定的类和结构体不能存在匿名联合体

绑定到元组和类元组的对象
先来看这段代码:
#include <tuple>
int main()
{
std::tuple<int, double, char> t{1, 2.2, 'c'};
const auto& [x, y, z] = t;
}
在C++ Insights可以看到被处理成:
#include <tuple>
int main()
{
std::tuple<int, double, char> t = std::tuple<int, double, char>{1, 2.2000000000000002, 'c'};
const std::tuple<int, double, char> & __t7 = t;
const int & x = std::get<0UL>(__t7);
const double & y = std::get<1UL>(__t7);
const char & z = std::get<2UL>(__t7);
return 0;
}
对于绑定元组和类元组对象有一系列条件:对于元组或者类元组类型T:
- 需要满足
std::tuple_size<T>::value是一个符合语法的表达式,并且该表达式获得的整数值与标识符列表中的别名个数相同。 - 类型
T还需要保证std::tuple_element<i, T>::type也是一个符合语法的表达式,其中i是小于std::tuple_size<T>::value的整数,表达式代表了类型T中第i个元素的类型。 - 类型
T必须存在合法的成员函数模板get<i>()或者函数模板get<i>(t),其中i是小于std::tuple_size<T>::value的整数,t是类型T的实例,get<i>()和get<i>(t)返回的是实例t中第i个元素的值。
下面是一个类元组对象支持结构化绑定的demo:
#include <iostream>
#include <tuple>
class BindBase3
{
public:
int a = 42;
};
class BindTest3 : public BindBase3
{
public:
double b = 11.7;
};
namespace std
{
template <>
struct tuple_size<BindTest3>
{
static constexpr size_t value = 2;
};
template <>
struct tuple_element<0, BindTest3>
{
using type = int;
};
template <>
struct tuple_element<1, BindTest3>
{
using type = double;
};
} // namespace std
template <std::size_t Idx>
auto& get(BindTest3& bt) = delete;
template <>
auto& get<0>(BindTest3& bt)
{
return bt.a;
}
template <>
auto& get<1>(BindTest3& bt)
{
return bt.b;
}
int main()
{
BindTest3 bt3;
auto& [x3, y3] = bt3;
x3 = 78;
std::cout << bt3.a << std::endl;
}
编译运行看效果:

参考资料
《现代C++语言核心特性解析》
https://zhuanlan.zhihu.com/p/158647883
https://hackingcpp.com/cpp/std/container_traversal.html
https://cppinsights.io/
1964

被折叠的 条评论
为什么被折叠?



