【现代C++学习】variant 总结

目录

  • 一、varaint 基础
  • 二、variant 初始化
  • 三、修改可变体的对象成员的四种方式
  • 四、可变体的对象成员的生命周期
  • 五、可变体的visit进阶
    • 5.1 实现元组的运行期索引
    • 5.2 访问模式
      • 1. 简单的访问模式
      • 2. 复杂的访问模式
      • 3. 模板访问
  • 六、简化树形数据结构的构建

参考来源:知乎_铁甲万能狗_C/C++ 修道院
参考来源:cppreference
参考来源:现代C++教程:第 4.3 节

一、varaint 基础

varaint可以看做增强的Union变体,优点:

  • 对复杂类型的封装能力
  • 切换类型时,可以自动调用析构函数
  • 可变体不会发生额外的堆内存分配
  • 可以检查内部当前的活动类型

重要函数

  • index:返回当前可变体内部对应的数据类型的索引
    	std::variant<int, std::string> v = "abc";
        std::cout << "v.index = " << v.index() << '\n'; // v.index = 1
        v = {};
        std::cout << "v.index = " << v.index() << '\n'; // v.index = 0
    
  • std::get:获取可变体对应的数据类型的值
    std::variant<int, float> v{12}, w;
    std::cout << std::get<int>(v) << '\n'; // 12
    w = std::get<int>(v);
    w = std::get<0>(v);
    //  std::get(v); // error: no double in [int, float]
    //  std::get<3>(v);      // error: valid index values are 0 and 1
    try {
    	w = 42.0f;
    	std::cout << std::get<float>(w) << '\n'; // ok, prints 42
    	w = 42;
    	std::cout << std::get<float>(w) << '\n'; // throws
    } catch (std::bad_variant_access const& ex) {
    	std::cout << ex.what() << ": w contained int, not float\n";
    } 
    
  • std::get_if:获取可变体对应的数据类型的值,在访问可变体时不会抛出bad_variant_access异常,提供了访问前的类型安全判断
    	auto check_value = [](const std::variant<int, float>& v) {
    		if(const int* pval = std::get_if<int>(&v)) {
    			std::cout << "variant value: " << *pval << '\n'; 
            } else {
            	std::cout << "failed to get value!" << '\n';
             }
        };
     
        std::variant<int, float> v{12}, w{3.f};
        check_value(v); // variant value: 12 
        check_value(w); // failed to get value!
    
  • std::holds_alternative:检查可变体对应类型是否是可切换的类型T
    	std::variant<int, std::string> v = "abc";
        std::cout << std::boolalpha << std::holds_alternative<int>(v) << '\n'; // flase
        std::cout << std::holds_alternative<std::string>(v) << '\n'; // true
    
  • std::variant_size_v:用于检测可变体内部可切换的数据类型的个数
    std::variant<int, float, std::string> v;
    static_assert(std::variant_size_v<decltype(v)> == 3);
    
  • std::visit:用于访问可变体中的当前处于活动状态的数据类型的实例
    // the variant to visit
    using var_t = std::variant<int, long, double, std::string>;
    
    // helper constant for the visitor #3
    template<class>
    inline constexpr bool always_false_v = false;
    
    int main()
    {
        std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
     
        for (auto& v: vec) {
            // 1. void visitor, only called for side-effects (here, for I/O)
            std::visit([](auto&& arg){ std::cout << arg; }, v);
     
            // 2. value-returning visitor, demonstrates the idiom of returning another variant
            var_t w = std::visit([](auto&& arg) -> var_t { return arg + arg; }, v);
     
            // 3. type-matching visitor: a lambda that handles each type differently
            std::cout << ". After doubling, variant holds ";
            std::visit([](auto&& arg) {
                using T = std::decay_t<decltype(arg)>;
                if constexpr (std::is_same_v<T, int>)
                    std::cout << "int with value " << arg << '\n';
                else if constexpr (std::is_same_v<T, long>)
                    std::cout << "long with value " << arg << '\n';
                else if constexpr (std::is_same_v<T, double>)
                    std::cout << "double with value " << arg << '\n';
                else if constexpr (std::is_same_v<T, std::string>)
                    std::cout << "std::string with value " << std::quoted(arg) << '\n';
                else 
                    static_assert(always_false_v<T>, "non-exhaustive visitor!");
            }, w);
        }
        /* 输出:
    	    10. After doubling, variant holds int with value 20
    		15. After doubling, variant holds long with value 30
    		1.5. After doubling, variant holds double with value 3
    		hello. After doubling, variant holds std::string with value "hellohello"
    	*/
    }
    

二、variant 初始化

  • 默认为第一个类型
    std::variant<std::string, double, float, int> v;
    std::cout << v.index() << "\n"; // 0
    
  • 如果可变体的第一个类型是聚合类型,则必须要有默认构造函数;否则可以没有,编译通过
    class Item {
    public:
    	Item(int, float) {}
    };
    
    class Items {
    public:
    	Items() = default;
    	Items(int, float) {}
    };
    
    int main()
    {
    	// std::variant v1;
    	// No matching constructor for initialization of 
    	// 'std::variant' 
    	// (aka 'variant, allocator>, int>')
    	std::variant<std::string, double, Item, int> v2;
    	std::cout << v2.index() << "\n"; // 0
    	v2 = Item(1, 2.);
    	std::cout << v2.index() << "\n"; // 0
    	std::variant<Items, double, std::string, int> v3;
    	std::cout << v3.index() << "\n"; // 0
    	return 0;
    }
    
  • 类型模糊的传参
    std::variant<std::string, double, float, int> v = 12.; // 目标是 float, 但编译器将匹配数据类型的最大精度: double
    std::cout << v.index() << "\n"; // 1
    std::variant<std::string, double, float, int> v = 12.f;
    std::cout << v.index() << "\n"; // 2
    
  • 显式调用std::in_place_index告知可变体对象要在内置启用哪一个数据类型来构造可变体对象的实例
    std::tuple<std::string, double, int> t("123", 4.5, 8);
    std::variant<std::string, double, int> v {std::in_place_index<1>, std::get<1>(t)};
    std::variant<std::string, double, int> vs {std::in_place_index<0>, "123"};
    

三、修改可变体的对象成员的四种方式

  • 方式1:赋值操作符
  • 方式2:通过get方法获取真正的对象,然后修改
  • 方式3:通过原地索引API匹配数据类型,然后构造传值达到修改值的目的
  • 方式4:emplace方法赋值
class Item {
public:
	Item(int, float) {}
};

int main() {
	using MixType = std::variant<int, float, std::vector<int>, std::string, Item>;
	MixType v;

	// 方式1: 赋值操作符
	v= 12;
	std::cout << std::get<0>(v) << "\n"; // 12 
	// 方式2: 通过 get 方法获取真正的对象, 然后修改
	v = 23.5f;
	std::cout << std::get<1>(v) << "\n"; // 23.5 
	// 方式3: 通过原地索引 API 匹配数据类型, 然后构造传值达到修改值的目的
	v = MixType(std::in_place_index<2>, { 1, 2, 3 });
	std::cout << std::get<2>(v).size() << "\n"; // 3
	// 方式4: emplace 方法赋值
	v.emplace<3>("hello world!");
	std::cout << std::get<3>(v) << "\n"; // hello world!
	return 0;
} 

四、可变体的对象成员的生命周期

class Items {
public:
	Items() = default;
	Items(int, float) {}
	~Items() { std::cout << "Deconstruct!\n"; }
};
int main()
{
	using MixType = std::variant<int, float, std::vector<int>, std::string, Items>;
	MixType v;
	v.emplace<4>(1, 2.); // Deconstruct!
	v = 1;
    std::cout << std::get<0>(v) << "\n"; // 1
}

五、可变体的visit进阶

5.1 实现元组的运行期索引

template <size_t n, typename... T>
constexpr std::variant<T...> _tuple_index(const std::tuple<T...>& tpl, size_t i)
{
	if constexpr (n >= sizeof...(T)) {
		// throw std::out_of_range(" 越界.");
		exit(-1);
	}
	if (i == n) {
		return std::variant<T...>{ std::in_place_index<n>, std::get<n>(tpl) };
	}
	return _tuple_index<(n < sizeof...(T) - 1 ? n + 1 : 0)>(tpl, i);
}

template <typename... T>
constexpr std::variant<T...> tuple_index(const std::tuple<T...>& tpl, size_t i)
{
	return _tuple_index<0>(tpl, i);
}

template <typename... Ts>
std::ostream & operator<< (std::ostream & s, std::variant<Ts...> const & v)
{
	std::visit([&](auto && x){ s << x;}, v);
	return s;
}

int main() {
	std::tuple<std::string, double, int> t("123", 4.5, 8);
    std::cout << tuple_index(t, 0) << "\n"; // 123
    std::cout << tuple_index(t, 1) << "\n"; // 4.5
    std::cout << tuple_index(t, 2) << "\n"; // 8
    return 0;
}

5.2 访问模式

通过实现一个甚至多个可变体对象以引用的方式传递给std::visit回调的函数进行访问

1. 简单的访问模式

int main()
{
	auto say = [] (const auto& t) {
        std::cout << t << ", nice to meet you!" << "\n";
    };
    std::variant<int, float, std::string> man = {"Bob"};
    std::visit(say, man); // Bob, nice to meet you!
}

2. 复杂的访问模式

class CalculatorVisitor {
public:
    float mFactor;
    explicit CalculatorVisitor (float factor) : mFactor(factor)
    {}
    void operator()(int& i) const
    {
        i *= static_cast<int>(mFactor);
    }
    void operator()(float & i) const
    {
        i *= static_cast<float>(mFactor);
    }
    void operator()(std::vector<int> & arr) const
    {
        for (int k : arr) {
            k *= 2;
        }
    }
};
int main()
{
	std::variant<int, float, std::vector<int>> v = 2.5f;
    std::visit(CalculatorVisitor(0.97f), v);
    std::cout << std::get<1>(v) << "\n"; // 2.425
	return 0;
}

调用过程:

  1. 第一步:传值调用CalculatorVisitor进行实例化
  2. 第二步:可变体对象传递给std::visit函数
    • 查看可变体内部的活动对象的数据类型
    • 访问可变体对象内部的活动对象
  3. 第三步:将可变体活动对象的值传递给CalculatorVisitor实例
    这次传递不会重复初始化,std::visit调用了访问者CalculatorVisitor的构造函数,意味着std::visit方法持有了该访问者对象public公开的所有成员函数的上下文的控制权,因此std::visit可以要求CalculatorVisitor实现不同的行为
  4. 第四步:std::visit通过匹配不同重载版本的operator()运算符的函数指针

3. 模板访问

// the variant to visit
using var_t = std::variant<int, long, double, std::string>;

// helper type for the visitor
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };

// explicit deduction guide (not needed as of C++20)
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

int main()
{
	std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
	for (auto& v: vec) {
		// 4. another type-matching visitor: a class with 3 overloaded operator()'s
		// Note: The `(auto arg)` template operator() will bind to `int` and `long`
		//       in this case, but in its absence the `(double arg)` operator()
		//       *will also* bind to `int` and `long` because both are implicitly
		//       convertible to double. When using this form, care has to be taken
		//       that implicit conversions are handled correctly.
		std::visit(overloaded {
			[](auto arg) { std::cout << arg << ' '; },
			[](double arg) { std::cout << std::fixed << arg << ' '; },
			[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
			}, v);
	}
	// 输出: 10 15 1.500000 "hello"
	return 0;
}	 
int main()
{
	std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
    for (auto& vs: vec) {
        std::visit(overloaded {
            [](auto && value) {
                if constexpr (std::is_same_v<decltype(value), int&>) {
                    value = 2 * value + 1;
                    std::cout << value << ' ';
                } else if constexpr (std::is_same_v<decltype(value), double&>) {
                    value = 3 * 1.5f * (value - 1);
                    std::cout << value << ' ';
                } else if constexpr (std::is_same_v<decltype(value), std::string&>) {
                    std::cout << "\"" << value << "\"\n";
                }
            }
        }, vs);
    }
	// 输出: 21 2.25 "hello"
	return 0;
}

六、简化树形数据结构的构建

class Node;
using Int = int;
using Float = float;
using Double = double;
using String = std::string;
using Null = std::monostate;
using NodeList = std::vector<Node>;
using Data = std::variant<Int, Float, Double, String, Null, NodeList>;

class Node {
public:
    std::string mName;
    Data mData;
    Node(std::string&& name, Data&& data) : mName(name), mData(data) {}
};

template<typename Stream>
Stream& operator<<(Stream& stream, const Null& null)
{
    stream << "null";
    return stream;
}

template<typename Stream>
Stream& operator<<(Stream& stream, const NodeList& data)
{
    std::cout << "[ ";
    for (const auto& d : data) {
        std::cout << d << ",";
    }
    std::cout << ']';
    return stream;
}
template<typename Stream>
Stream& operator<<(Stream& stream, Data& data)
{
    std::visit([&](auto& val) {
        stream << val;
    }, data);
    return stream;
}

template<typename Stream>
Stream& operator<<(Stream& stream, const Node& node)
{
    std::cout << "Node { name='" << node.mName << "', data=" << node.mData << " }";
    return stream;
}

struct Person{
    std::string name;
    int age;
};

template<typename T, typename Fn>
NodeList ToTree(const std::vector<T>& list, Fn callback)
{
    NodeList result;
    for (const auto& v: list) {
        result.push_back(callback(v));
    }
    return result;
}

int main()
{
    auto root = Node(
        "Match", NodeList({
            Node("TeamA", NodeList({
                Node("AA", 72),
                Node("AB", 55),
                Node("AC", 69)
            })),
            Node("TeamB", NodeList({
                Node("BA", 80),
                Node("BB", 57),
                Node("BC", 64)
            }))
        })
    );
    std::cout << root << "\n";

    std::vector<Person> vp = { { "Bob", 10 }, { "Jane", 22 } };
    auto pRoot = ToTree(vp, [] (auto&& arg) {
        std::string name = arg.name;
        Node x(std::move(name), arg.age);
        return x;
    });
    std::cout << pRoot << "\n";
    
    return 0;
}

你可能感兴趣的:(C++,c++)