1. 程式人生 > >C++17嚐鮮:variant

C++17嚐鮮:variant

variant

variant 是 C++17 所提供的變體型別。variant<X, Y, Z> 是可存放 X, Y, Z 這三種類型資料的變體型別。

  • 與C語言中傳統的 union 型別相同的是,variant 也是聯合(union)型別。即 variant 可以存放多種型別的資料,但任何時刻最多隻能存放其中一種型別的資料。
  • 與C語言中傳統的 union 型別所不同的是,variant 是可辨識的型別安全的聯合(union)型別。即 variant 無須藉助外力只需要通過查詢自身就可辨別實際所存放資料的型別。

v = variant<int, double, std::string>

,則 v 是一個可存放 int, double, std::string 這三種類型資料的變體型別的物件。

  • v.index() 返回變體型別 v 實際所存放資料的型別的下標。
    變體中第1種類型下標為0,第2種類型下標為1,以此類推。
  • std::holds_alternative<T>(v) 可查詢變體型別 v 是否存放了 T 型別的資料。
  • std::get<I>(v) 如果變體型別 v 存放的資料型別下標為 I,那麼返回所存放的資料,否則報錯。
    std::get_if<I>(&v) 如果變體型別 v 存放的資料型別下標為 I,那麼返回所存放資料的指標,否則返回空指標。
  • std::get<T>(v) 如果變體型別 v 存放的資料型別為 T,那麼返回所存放的資料,否則報錯。
    std::get_if<T>(&v) 如果變體型別 v 存放的資料型別為 T,那麼返回所存放資料的指標,否則返回空指標。
#include <iostream>
#include <string>
#include <variant>

using namespace std;

int main()
{
    variant<int, double, string> v; // v == 0
    v = 1
; bool has_int = holds_alternative<int>(v); bool has_double = holds_alternative<double>(v); cout << v.index() << has_int << has_double << get<0>(v) << *get_if<0>(&v) << endl; // 01011 v = 2.0; cout << v.index() << (get_if<int>(&v) == nullptr) << get<1>(v) << get<double>(v) << endl; // 1122 v = "a"; cout << v.index() << get<2>(v) << get<string>(v) << endl; // 2aa }

std::visit

std::visit(f, v) 將變體型別 v 所存放的資料作為引數傳給函式 f。
std::visit(f, v, u) 將變體型別 v, u 所存放的資料作為引數傳給函式 f。

std::visit 能將所有變體型別引數所存放的資料作為引數傳給函式引數。

#include <iostream>
#include <string>
#include <variant>
#include <boost/hana/functional/overload.hpp>

using namespace std;
namespace hana = boost::hana;

struct Visitor {
    void operator()(int n) const {
        cout << "int: " << n << endl;
    }
    void operator()(double d) const {
        cout << "double: " << d << endl;
    }
    void operator()(const string& s) const {
        cout << "string: " << s << endl;
    }
};

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

int main()
{
    variant<int, double, string> v; // v == 0
    auto f = [](auto& x) {cout << x << endl;};
    Visitor f2;
    overloaded f3{
        [](int n){cout << "int: " << n << endl;},
        [](double d){cout << "double: " << d << endl;},
        [](const string& s){cout << "string: " << s << endl;}
    };
    auto f4 = hana::overload(
        [](int n){cout << "int: " << n << endl;},
        [](double d){cout << "double: " << d << endl;},
        [](const string& s){cout << "string: " << s << endl;}
    );
    auto f5 = [](auto& arg) { using T = decay_t<decltype(arg)>; 
//  auto f5 = []<typename T>(T& arg) { // C++ 20
        if constexpr (is_same_v<T, int>) {
            cout << "int: " << arg << endl;
        }
        else if constexpr (is_same_v<T, double>) {
            cout << "double: " << arg << endl;
        }
        else if constexpr (is_same_v<T, string>) {
            cout << "string: " << arg << endl;
        }
    };
    v = 1; visit(f, v); visit(f2, v); visit(f3, v); visit(f4, v); visit(f5, v); // 1 int: 1 int: 1 int: 1 int: 1
    v = 2.0; visit(f, v); visit(f2, v); visit(f3, v); visit(f4, v); visit(f5, v); // 2 double: 2 double: 2 double: 2 double: 2
    v = "a"; visit(f, v); visit(f2, v); visit(f3, v); visit(f4, v); visit(f5, v); // a string: a string: a string: a string: a
}
  • f 和 f5 是泛型 lambda,接受所有引數的型別。
    f 不分辨引數型別。
    f5 通過編譯期 if 語句來分辨引數型別。
  • f2 和 f3 是函式物件,通過過載函式呼叫操作符來分辨引數的型別。
    f2 的函式呼叫操作符由自身定義。
    f3 的函式呼叫操作符繼承自3個lambda。
  • f4 這個函式物件經由 boost::hana::overload 函式生成,該函式所生成的函式物件能從多個lambda引數中選取一個合適的來呼叫指定引數。