1 std::get()
1.1 用于 std::pair 和 std::tuple
在介绍 std::pair 和 std::tuple 的时候已经介绍过如何用 std::get() 获取对应位置上的值,用法大致如此:
std::pair<int, double> p(42, 5.1);
assert(std::get<0>(p) == p.first);
std::get<0>(p) = 5;
assert(std::get<0>(p) == 5);std::tuple<int, double, char> t(42, 5.1, 'A');
assert(std::get<0>(t) == 42);
std::get<0>(t) = 5;
assert(std::get<0>(t) == 5);
特别的,在 C++14 之后,还支持通过类型匹配操作 std::pair 和 std::tuple 的元素,比如:
std::tuple<int, double, std::string> t(42, 5.1, "Kitty");
assert(std::get<0>(t) == std::get<int>(t));
std::get<std::string>(t) = "Fin";
1.2 用于 std::array
std::get() 也可用于 std::array 容器,用与获取某个位置上的值,
std::array<int, 5> arr {1, 2, 3, 4, 5};
assert(std::get<3>(arr) == 4)
std::get<3>(arr) = 10;
assert(arr[3] == 10);
1.3 用于 std::variant
C++17 引入了 std::variant,基本上可以用来取代传统的 union,并且在使用上更方便,类型更安全。 std::get() 也可用于 std::variant,根据类型匹配操作相应的值:
std::variant<int, double, std::string> ifs = 10;
assert(std::get<int>(ifs) == 10);
assert(std::get<0>(ifs) == 10); //等效,因为 int 是第一个位置std::variant<int, std::string> is = "Kitty";
std::cout << std::get<std::string>(is) << std::endl; // 输出 Kitty
1.4 关于异常
std::get() 对于无效的索引参数或类型匹配失败,会抛出一个 std::bad_variant_access 异常,所以使用 std::get(),尤其是对于 std::variant 这种实际类型经常与想象不一致的场合,使用 try 是必须的,否则程序会死的很难看:
std::variant<int, float> v{12};try
{auto a = std::get<std::string>(v);
}
catch (std::bad_variant_access&)
{ ... }
1.5 用于自定义类型
标准库提供的 std::get() 函数之所以能用于前面介绍的几种类型,是因为标准库提供了针对这些类型的特化版本,以 std::array 为例,标准库提供了四个特化版本(重载):
template <size_t N, class T, size_t _Size>
constexpr T& get(array<T, _STize>& _Arr) noexcept;template <size_t N, class T, size_t _Size>
constexpr const T& get(const array<T, _Size>& _Arr) noexcept;template <size_t N, class T, size_t _Size>
constexpr T&& get(array<T, _Size>&& _Arr) noexcept;template <size_t N, class T, size_t _Size>
constexpr const T&& get(const array<T, _Size>&& _Arr) noexcept;
如果我们希望自定义的类型也能支持 std::get() 函数,只需要给自定义的类型也实现一套对应的特化版本即可。假设有这样一个自定义对象 FooTest,它有三个成员变量:
struct FooTest
{FooTest(const std::string& n, int a, double w){name = n;age = a;weight = w;}std::string name;int age;double weight;
};
我们希望能提供一个功能,允许用户通过这三个成员变量的顺序获取它们,比如用 std::get<0>(ft) 获取 ft.name 的值。具体做起来其实也非常简单,就是针对自定义的类型,比葫芦画瓢,实现这四个全局函数即可:
template <std::size_t N>
auto& get(FooTest& f) noexcept
{static_assert(N < 3);if constexpr (N == 0) return f.name;else if constexpr (N == 1) return f.age;else return f.weight;
}template <std::size_t N>
const auto& get(const FooTest& f) noexcept
{static_assert(N < 3);if constexpr (N == 0) return f.name;else if constexpr (N == 1) return f.age;else return f.weight;
}template <std::size_t N>
auto&& get(FooTest&& f) noexcept
{static_assert(N < 3);if constexpr (N == 0) return std::move(f.name);else if constexpr (N == 1) return std::move(f.age);else return std::move(f.weight);
}template <std::size_t N>
const auto&& get(const FooTest&& f) noexcept
{static_assert(N < 3);if constexpr (N == 0) return std::move(f.name);else if constexpr (N == 1) return std::move(f.age);else return std::move(f.weight);
}
关于 static_assert 和 constexpr 的意义在《C++ 的 if-constexpr》这篇主题中已经介绍过,使用 if-constexpr 需要编译器支持 C++ 17,如果你的编译器不支持 C++ 17,你可以考虑使用特化版本提供它们。以获取 name 属性为例,针对 get<0> 进行特化:
template <>
auto& get<0>(FooTest& f) noexcept
{ return f.name; }template <>
const auto& get<0>(const FooTest& f) noexcept
{ return f.name; }template <>
auto&& get<0>(FooTest&& f) noexcept
{ return std::move(f.name); }template <>
const auto&& get<0>(const FooTest&& f) noexcept
{ return std::move(f.name); }
其他两个属性分别实现 get<1> 和 get<2> 的特化版本即可。
这样就可以使用自己的 get() 函数 捣鼓出来一些奇奇怪怪的代码了,比如:
FooTest f("Kitty", 3, 2.7);std::string name = std::get<0>(f);
std::get<0>(f) = "Doggy";
如果不怕辣眼睛的话,可以把他们加到 std 空间中,不过,为了避免自定义的类型与标准库中的类型出现名字冲突,最好给自定义类型 FooTest 加上全局域的限定,比如:
namespace std {template <std::size_t N>const auto&& get(const ::FooTest&& f) noexcept;
}
2 std::get_if()
std::get() 对于错误的索引或不能找到类型匹配的时候,会抛出异常,那么对于不希望触发异常的场合怎么办呢?C++ 17 很贴心的提供了一个 std::get_if(),用法和 std::get() 类似,但是不会抛出异常。目前 std::get_if() 只能用于 std::variant 类型,其原型如下:
template< std::size_t I, class... Types >
constexpr std::add_pointer_t<std::variant_alternative_t<I, std::variant<Types...>>>get_if( std::variant<Types...>* pv ) noexcept;template< std::size_t I, class... Types >
constexpr std::add_pointer_t<const std::variant_alternative_t<I, variant<Types...>>>get_if( const std::variant<Types...>* pv ) noexcept;
这个函数返回一个指针,指针的类型与返回值的类型一致,可以通过 * 提领该指针指向的内容。当索引非法或指定的类型与 std::variant 实际类型不匹配时,它返回一个空指针。
std::variant<int, float> v{12};if(auto pval = std::get_if<int>(&v))std::cout << "variant value: " << *pval << std::endl;
else std::cout << "failed to get value!" << std::endl;
关注作者的算法专栏
https://blog.csdn.net/orbit/category_10400723.html
关注作者的出版物《算法的乐趣(第二版)》
https://www.ituring.com.cn/book/3180