c++11通用工具:tuple

關於pair的構造函數

c++11裏 std::pair<T1,T2>::pair 的構造函數有:

constexpr pair();
pair(const T1& x, const T2& y);
template <class U1, class U2> pair(U1&& x, U2&& y);
template <class U1, class U2> pair(const pair<U1, U2>& p);
template <class U1, class U2> pair(pair<U1, U2>&& p);
template <class... Args1, class... Args2>
  pair(std::piecewise_construct_t,
       std::tuple<Args1...> first_args,
       std::tuple<Args2...> second_args);
pair(const pair& p) = default;
pair(pair&& p) = default;

來自stackoverflow的一個問題 “why do i need to use piecewise_construct in map::emplace for single arg constructors of noncopyable objects?” 的解答中說明了 pair 的一些問題,以下內容是一個簡述。

#include <map>
struct A
{
    A(int) {}
    A(A&&) = delete;
    A(A const&) = delete;
};
int main()
{
    std::pair<int, A> x(1, 4); // error
}

std::pair<int, A> x(1, 4); 期望使用 template <class U1, class U2> pair(U1&& x, U2&& y);,但 cppreference 對於該構造函數說:

以 std::forward(x) 初始化 first 並以 std::forward(y) 初始化 second 。
此構造函數參與重載決議,當且僅當 std::is_constructible_v<first_type, U1&&> 和 std::is_constructible_v<second_type, U2&&> 均爲 true 。
此構造函數爲 explicit ,當且僅當 std::is_convertible_v<U1&&, first_type> 爲 false 或 std::is_convertible_v<U2&&, second_type> 爲 false 。

因爲A的移動構造是delete的,所以該版本的構造函數不能使用。
然後去嘗試 pair(const T1& x, const T2& y);,因爲A的複製構造也是delete的,這個版本的構造函數也不能用。
所以找不到可用的構造函數了。
這可咋整,注意 pair 的第6個構造函數:

轉發 first_args 的元素到 first 的構造函數並轉發 second_args 的元素到 second 的構造函數。這是能用於構造不可複製不可移動類型的 pair 的僅有的非默認構造函數。

std::piecewise_construct_t 就是用於這種情形的一個專用類型,std::piecewise_construct是它的一個對象。改爲 std::pair<int, A> x(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(4)); 搞定。


因爲 map 的 value_type 是 pair<const K, V>,map::emplace 會將參數全部轉發給 value_type 進行原地構造,所以會出現這種代碼:

#include <map>
struct A
{
    A(int) {}
    A(A&&) = delete;
    A(const A&) = delete;
};
int main()
{
    std::map<int, A> map;
    map.emplace(1, 2); // doesn't work
    map.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(2)); // OK
}

另外,對於 copy-initialization(用等號初始化):A t = 4;,會先創建一個臨時的A,然後用這個臨時對象初始化t,c++98裏是調拷貝構造,c++11則是調移動構造。
但是,看起來只是形式上需要拷貝構造或移動構造(將他們聲明爲delete或private的將會編譯不通過),實際上並沒有調用到(通過在內部調用printf測試),或許是編譯器進行了優化。

tuple

接下來介紹下 tuple。tuple 是 pair 的推廣,可以保存多個不同類型的元素。
有幾個函數可以用來創建 tuple:

//返回類型由參數推斷而來
template <class... Types>
constexpr tuple<VTypes...> make_tuple(Types&&...);
template<class... Types>
tuple<Types&&...> forward_as_tuple (Types&&... args) noexcept;
template<class... Types>
constexpr tuple<Types&...> tie(Types&...) noexcept;
template <class... Tuples>
constexpr tuple<CTypes...> tuple_cat(Tuples&&...);

std::forward_as_tuple 創建用於轉發的 tuple,上節已經用過了。
std::tie 創建到其參數或 std::ignore 實例的左值引用的 tuple,std::ignore 是用 tie 解包 tuple 時用來跳過元素的佔位符,例如:

std::map<int,int> m;
bool result;
//tuple可以從pair構造或賦值,如果tuple只有兩個元素的話
std::tie(std::ignore, result) = m.insert(std::make_pair(1,2));

std::tuple_cat 可以將多個 tuple 連接成新的 tuple,如:

#include <iostream>
#include <tuple>
#include <string>

template<class Tuple, std::size_t N>
struct TuplePrinter {
    static void print(const Tuple& t) 
    {
        TuplePrinter<Tuple, N-1>::print(t);
        std::cout << ", " << std::get<N-1>(t);
    }
};
 
template<class Tuple>
struct TuplePrinter<Tuple, 1> {
    static void print(const Tuple& t) 
    {
        std::cout << std::get<0>(t);
    }
};
 
template<class... Args>
void print(const std::tuple<Args...>& t) 
{
    std::cout << "(";
    TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
    std::cout << ")\n";
}
 
int main()
{
    std::tuple<int, std::string, float> t1(10, "Test", 3.14);
    int n = 7;
    auto t2 = std::tuple_cat(t1, std::make_tuple("Foo", "bar"), t1, std::tie(n));
    n = 10;
    print(t2);
}
//(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)

上面用到了 std::get,它從 tuple 中提取元素,另外它還能從 std::pair 和 std::array 中提取元素。
std::tuple_size 用來獲取 tuple,pair,array 的大小:

#include <iostream>
#include <utility>
#include <tuple>
#include <array>

template<class T>
void test(T t)
{
    int a[std::tuple_size<T>::value]; // 可用於編譯時
    std::cout << std::tuple_size<T>::value << '\n'; // 或運行時
}

int main()
{
    test(std::make_tuple(1, 2, 3.14));
    test(std::make_pair(1, 3.14));
    std::array<float, 3> arr;
    test(arr);
}

std::tuple_element 用來獲取 tuple,pair,array 元素的類型,如:

#include <iostream>
#include <tuple>
 
template <class... Args>
struct type_list
{
   template <std::size_t N>
   using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
};
 
int main()
{
   std::cout << std::boolalpha;
   type_list<int, char, bool>::type<2> x = true;
   std::cout << x << '\n';
}

參考 <tuple>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章