接著上一篇文章《【C++ Template Meta-Programming 與 Standard Template Library】實作 <type_traits> (上)》

首先, 為了方便, 我將運用樣板別名和樣板引數. 在 C++ 14 之後, 標頭檔 <type_traits> 引入了 traits_t 和 traits_v 這樣的別名, 其中 _t 後綴表示樣板別名, _v 後綴表示樣板引數. 舉個例子, 對於 std::add_pointer, 一般對於型別 T, 我們要獲取其增加一個指標之後的型別, 要寫成 typename std::add_pointer<T>::type. 使用了帶有 _t 後綴的樣板別名之後, 就可以寫成 std::add_pointer_t<T> 對於 std::is_same, 要想判定型別 TU 是否一致, 要寫成 std::is_same<T, U>::value. 使用了帶有 _v 後綴的樣板引數之後, 我們只要寫成 std::is_same_v<T, U> 即可

本篇文章所有的型別萃取都非常複雜, 建議反覆觀看以理解

1. std::decay

這個超函式的作用是對於任意給定的型別引數, 去除其 const 限定、volatile 限定和參考限定. 除此之外, 讓函式型別退化為函式指標型別, 讓陣列型別退化為陣列指標型別 :

template <typename T>
struct decay {
    using type = conditional_t<
            is_array_v<remove_reference_t<T>>,
            add_pointer_t<remove_extent_t<remove_reference_t<T>>>,
            conditional_t<is_function_v<remove_reference_t<T>>, add_pointer_t<remove_reference_t<T>>, remove_cv_t<remove_reference_t<T>>>>;
};

2. std::result_of, std::invoke_result, std::is_invocable, std::is_nothrow_invocable, std::is_invocable_rstd::is_nothrow_invocable_r

這兩個超函式都可以獲取一個可呼叫物件的回傳型別. 其中, std::result_of 的使用必須要給出一個可呼叫物件對應的函式型別, 而 std::invoke_result 需要給出可呼叫物件的型別以及引數型別 :

#include <iostream>

using namespace std;
struct callable {
    void *operator()(int, char, decltype(typeid(int))) const noexcept {
        return nullptr;
    }
};
int main(int argc, char *argv[]) {
    cout << boolalpha;
    cout << is_same_v<result_of_t< /* 注意這裏和下面的區別 */callable (int, char, decltype(typeid(int)))>, void *> << endl;        //輸出結果 : true
    cout << is_same_v<invoke_result_t<callable, int, char, decltype(typeid(int))>, void *> << endl;     //輸出結果 : true
}

這裏要注意的是, 可呼叫物件不一定指的是函式, 對成員變數的存取也可以當作可呼叫物件的呼叫, 其回傳型別是對應成員變數的型別. 因此, 只要所有給定的型別都是已經定義的完整型別, 甚至未給定邊界的陣列型別和 void 系型別, 都可以應用這兩個超函式. 但是, 若給定的引數是不可呼叫的, 那麼會產生編碼錯誤

此時, 我們可以運用 SFINAE, 針對成員函式的呼叫、成員變數的存取和普通函式的呼叫進行實作 :

首先我們需要定義一個表示呼叫失敗的標誌型別 :

struct invoke_failure_tag {};

另外, C++ 標準樣板程式庫中 std::invoke_resultstd::result_of 都考慮了 std::reference_wrapper 的情況. 此處, 我們暫時不考慮. 我們首先實作成員變數函式的兩種情況. 對成員函式的呼叫, 除了可以通過 "." 運算子之外, 還可以通過成員函式指標配合 ".*" 運算子進行, 因此我們要將兩種情況都納入其中 :

/* 針對一般形式 */
template <typename Fp, typename T, typename ...Args>
static constexpr decltype((declval<T>().*declval<Fp>())(declval<Args>()...)) test_member_function(int) noexcept;
template <typename ...>
static constexpr invoke_failure_tag test_member_function(...) noexcept;

template <typename Fp, typename T, typename ...Args>
static consteval inline bool is_nothrow_member_function() noexcept {
    return noexcept((declval<T>().*declval<Fp>())(declval<Args>()...));
}

/* 針對成員函式指標形式 */
template <typename Fp, typename Ptr, typename ...Args>
static constexpr decltype((declval<Ptr>()->*declval<Fp>())(declval<Args>()...)) test_member_function_deref(int) noexcept;
template <typename ...>
static constexpr invoke_failure_tag test_member_function_deref(...) noexcept;

template <typename Fp, typename Ptr, typename ...Args>
static consteval inline bool is_nothrow_member_function_deref() noexcept {
    return noexcept((declval<Ptr>()->*declval<Fp>())(declval<Args>()...));
}

其中, 名稱中帶有 nothrow 的函式使用了 noexcept 運算子測試了呼叫是否不擲出任何例外情況. 這是為了 std::is_nothrow_invocablestd::is_nothrow_invocable_r 準備的. 上面的兩個部分都是為通過類別物件呼叫成員函式準備的, 所以我們可以看到必須使用 std::declval 對類別產生對應的物件

與成員函式的呼叫類似, 我們可以實作出針對成員物件存取的測試函式 :

/* 針對一般形式 */
template <typename Op, typename T>
static constexpr decltype(declval<T>().*declval<Op>()) test_member_object(int) noexcept;
template <typename, typename>
static constexpr invoke_failure_tag test_member_object(...) noexcept;

template <typename Op, typename T>
static consteval inline bool is_nothrow_member_object() noexcept {
    return noexcept(declval<T>().*declval<Op>());
}


/* 針對成員變數指標形式 */
template <typename Op, typename Ptr>
static constexpr decltype(declval<Ptr>->*declval<Op>()) test_member_object_deref(int) noexcept;
template <typename, typename>
static constexpr invoke_failure_tag test_member_object_deref(...) noexcept;

template <typename Op, typename Ptr>
static consteval inline bool is_nothrow_member_object_deref() noexcept {
    return noexcept(declval<Ptr>()->*declval<Op>());
}

而針對普通函式的測試函式非常簡單, 因為一般的可呼叫物件直接通過函式呼叫運算子就可以呼叫 :

template <typename F, typename ...Args>
static constexpr decltype(declval<F>()(declval<Args>()...)) test_function(int) noexcept;
template <typename ...>
static constexpr invoke_failure_tag test_function(...) noexcept;

template <typename F, typename ...Args>
static consteval inline bool is_nothrow_function() noexcept {
    return noexcept(declval<F>()(declval<Args>()...));
}

得到這些測試函式之後, 我們需要通過超函式獲得呼叫結果對應的型別. 在此之前, 我們宣告一個未實作的類別 :

template <typename, typename>
struct result_of_member_object;

第一個樣板引數接受成員指標的型別, 第二個引數接受要測試存取的型別. 接下來可以針對這個樣板特製化 :

template <typename PreT, typename Class, typename T>
struct result_of_member_object<PreT Class::*, T> {
    using type = conditional_t<
            is_same_v<remove_cvref_t<T>, Class> or is_base_of_v<Class, remove_cvref_t<T>>,
            decltype(test_member_object<PreT Class::*, T>(0)),
            decltype(test_member_object_deref<PreT Class::*, T>(0))>;
};

一般來說, 若我們要測試某個類別中是否存在可存取的 T 型別的成員, 是需要將繼承考慮進來的. 如果型別 T 是給定引數型別 Class 的衍生類別, 或者和型別 Class 相同, 那麼是通過物件進行訪問的; 否則, 就是通過成員物件指標的形式來訪問的. 以此, 我們來區分兩者是否使用帶有解參考的那個測試函式. 這個超函式的使用方法為, 對於給定的成員函式指標 PreT Class::*, 是否可以通過型別 T 來訪問這個成員. 當 T 不是指標的時候, 其訪問方法為 std::declval<T>().*std::declval<PreT Class::*>(); 如果 T 是指標型別, 那麼訪問方法為 std::declval<T>()->*std::declval<PreT Class::*>(). 最後, 對於給定的型別引數, 我們還看到要去除其參考限定、const 限定和 volatile 限定

有了這個經驗之後, 我們很容易就實作出 result_of_member_function, result_of_nothrow_objectresult_of_nothrow_function :

template <typename, typename>
struct result_of_nothrow_member_object;
template <typename PreT, typename Class, typename T>
struct result_of_nothrow_member_object<PreT Class::*, T> :
        conditional_t<is_same_v<remove_cvref_t<T>, Class> or is_base_of_v<Class, remove_cvref_t<T>>,
        bool_constant<is_nothrow_member_object<PreT Class::*, T>()>,
        bool_constant<is_nothrow_member_object_deref<PreT Class::*, T>()>> {};

template <typename ...>
struct result_of_member_function;
template <typename PreT, typename Class, typename Fp, typename ...Args>
struct result_of_member_function<PreT Class::*, Fp, Args...> {
    using type = conditional_t<
            is_same_v<remove_cvref_t<Fp>, Class> or is_base_of_v<Class, remove_cvref_t<Fp>>,
            decltype(test_member_function<PreT Class::*, Fp, Args...>(0)),
            decltype(test_member_function_deref<PreT Class::*, Fp, Args...>(0))>;
};

template <typename ...>
struct result_of_nothrow_member_function;
template <typename PreT, typename Class, typename Fp, typename ...Args>
struct result_of_nothrow_member_function<PreT Class::*, Fp, Args...> :
        conditional_t<is_same_v<remove_cvref_t<Fp>, Class> or is_base_of_v<Class, remove_cvref_t<Fp>>,
        bool_constant<is_nothrow_member_function<PreT Class::*, Fp, Args...>()>,
        bool_constant<is_nothrow_member_function_deref<PreT Class::*, Fp, Args...>()>> {};

對於成員函式和成員變數的處理, 我們都已經寫好了, 接下來我們需要把它們整合起來 :

template <bool, bool, typename, typename ...>
struct result_of_auxiliary : invoke_failure_tag {};

其中, 第一個給定的樣板引數表示是否存取成員物件, 第二個給定的樣板引數表示是否存取成員函式. 如果兩個都為 false, 那麼就是呼叫普通的可呼叫物件

template <typename MenPtr, typename T>
struct result_of_auxiliary<true, false, MenPtr, T> : conditional_t<
        is_same_v<invoke_failure_tag, typename result_of_member_object<decay_t<MenPtr>, T>::type>,
        invoke_failure_tag,
        result_of_member_object<decay_t<MenPtr>, T>> {};

template <typename MenPtr, typename T, typename ...Args>
struct result_of_auxiliary<false, true, MenPtr, T, Args...> : conditional_t<
        is_same_v<invoke_failure_tag, typename result_of_member_function<decay_t<MenPtr>, T, Args...>::type>,
        invoke_failure_tag,
        result_of_member_function<decay_t<MenPtr>, T, Args...>> {};

template <typename F, typename ...Args>
struct result_of_auxiliary<false, false, F, Args...> : conditional_t<
        is_same_v<invoke_failure_tag, decltype(test_function<F, Args...>(0))>,
        invoke_failure_tag,
        type_identity<decltype(test_function<F, Args...>(0))>> {};

對於成員變數存取, 給定的型別 T 來說, 我們還要解除其覆蓋, 即 unwrap, 但是此處我們不考慮這種情況. 如果要考慮的話, 只需要實作 unwrap 超函式, 然後將 T 替換為 typename unwrap<T>::type 即可. 我們還注意到這裡使用了 std::decay, 這是因為給定的引數很可能帶有各種限定, 而 result_of_member_objectresult_of_member_function 並沒有考慮到這些限定的情況, 因此此處要移除

對於 result_of_nothrow_auxiliary 也是類似, 但是它不是預設繼承自 invoke_failure_tag, 而是繼承自 std::false_type. 因為是否為不擲出例外情況, 只有 std::true_typestd::false_type 才能表達 :

template <bool, bool, typename ...>
struct result_of_nothrow_auxiliary : false_type {};

模仿 result_of_auxiliary, 我們可以實作出

template <typename MemPtr, typename T>
struct result_of_nothrow_auxiliary<true, false, MemPtr, T> :
        result_of_nothrow_member_object<decay_t<MemPtr>, T> {};

template <typename MemPtr, typename T, typename ...Args>
struct result_of_nothrow_auxiliary<false, true, MemPtr, T, Args...> :
        result_of_nothrow_member_function<decay_t<MemPtr>, T> {};

template <typename F, typename ...Args>
struct result_of_nothrow_auxiliary<false, false, F, Args...> :
        bool_constant<is_nothrow_function<F, Args...>()> {};

最終, 我們可以實作出 std::invoke_resultstd::result_of :

template <typename Ptr, typename ...Args>
struct invoke_result : result_of_auxiliary<is_member_object_pointer_v<remove_reference_t<Ptr>>,
        is_member_function_pointer_v<remove_reference_t<Ptr>>, Ptr, Args...> {
    static_assert(is_complete_v<Ptr>, "The first template argument must be a complete type!");
};

template <typename ...>
struct result_of;
template <typename F, typename ...Args>
struct result_of<F (Args...)> : invoke_result<F, Args...> {};

但是上面我們在實作的時候, 你會發現我讓輔助的 result_of 系列超函式繼承了 invoke_failure_tag, 這是為了實作 is_invocable 系列的超函式準備的. 要判定是否可以呼叫, 我們只需要再加一個輔助性的類別 :

template <typename, typename = void>
struct is_invocable_auxiliary : false_type {};
template <typename Result>
struct is_invocable_auxiliary<Result, void_t<typename Result::type>> : true_type {};

通過上面的實作, 我們注意到, 對於可以呼叫的時候, invoke_result 中存在一個名稱為 type 的回傳型別; 但是如果不能呼叫的時候, 這個名稱為 type 的型別別名就不會存在於 std::invoke_result 中, 因此我們在樣板偏特製化中運用 SFINAE, 達到檢測的目的, 便可以實作 std::is_invocable :

template <typename F, typename ...Args>
struct is_invocable : is_invocable_auxiliary<invoke_result<F, Args...>> {};

std::is_invocable_r 除了用於檢測呼叫之外, 還用於檢測回傳型別是否可以向另外一個型別發生隱含型別轉化 :

template <typename R, typename F, typename ...Args>
struct is_invocable_r : conditional_t<is_invocable_v<F, Args...>,
        conditional_t<is_convertible_v<typename invoke_result<F, Args...>::type, R>, true_type, false_type>,
        false_type> {};

最後對於 std::is_nothrow_invocablestd::is_nothrow_invocable_r, 唯一需要注意的地方是 : std::is_nothrow_invocable_r 還要求隱含型別轉化是不擲出例外情況的

template <typename Ptr, typename ...Args>
struct is_nothrow_invocable : result_of_nothrow_auxiliary<
        is_member_object_pointer_v<remove_reference_t<Ptr>>,
        is_member_function_pointer_v<remove_reference_t<Ptr>>, Ptr, Args...> {};

template <typename From, typename To>
struct is_nothrow_convertible : conditional_t<
        is_void_v<From> and is_void_v<To>,
        true_type,
        conditional_t<is_convertible_v<From, To>, decltype(test_nothrow_convertible<To>(declval<From>())), false_type>> {};

其中, test_nothrow_convertible 可以實作為

template <typename T>
static constexpr true_type test_convertible(T) noexcept;

static constexpr false_type test_convertible(...) noexcept;

template <typename T>
static constexpr void test_noexcept(T) noexcept;

template <typename T>
static constexpr bool_constant<noexcept(test_noexcept<T>())> test_nothrow_convertible() noexcept;

3. std::make_signed, std::make_unsigned, integral_promotion, floating_promotion, character_promotionarithmetic_promotion

對於任意型別, 上述超函式都是去適配某個型別. 對於 std::make_signedstd::make_unsigned 來說, 它們是去尋找 sizeof 值和給定的樣板引數 T 最接近的型別, 這個型別根據實際情況是有號數或者無號數的; 而 promotion 系列的超函式是去尋找一個 sizeof 值比 T 大的型別. 而回傳型別必定是內建的算術型別, 包括整型型別和浮點數型別

要實作這些超函式, 我們需要借助文章《【C++ Template Meta-Programming 與 Standard Template Library】實作 <type_traits> (上)》中的 type_container. 首先, 我們把整型和浮點數型別根據其本身的屬性使用 type_container 進行收集 :

using signed_integral = type_container<signed char, signed short, signed int, signed long, signed long long, __int128_t>;
using unsigned_integral = type_container<unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long, __uint128_t>;
using floating_point = type_container<float, double, long double>;
using character = type_container<char8_t, char16_t, char32_t>;

注意, character 中我們遺棄了 char, unsigned char, signed charwchar_t, 這是因為它只用於 character_promotion, 而這些被遺棄的型別在這裡沒有太大用處, 我們統一為 UTF 字元型別

接下來我們實作 std::make_unsignedstd::make_signed 的輔助超函式 :

template <typename T, typename Result, typename Container>
struct make_signed_or_unsigned_auxiliary {
private:
    using next_type = typename Container::type;
    using remaining_type = typename Container::remaining;
public:
    using type = conditional_t<
        sizeof(T) == sizeof(Result),
        Result,
        conditional_t<
                (sizeof(T) > sizeof(Result)) and sizeof(T) <= sizeof(next_type),
                next_type,
                typename make_signed_or_unsigned_auxiliary<T, next_type, remaining_type>::type
        >>;
};
template <typename T, typename Result>
struct make_signed_or_unsigned_auxiliary<T, Result, type_container<>> {
    using type = Result;
};

理解起來非常簡單, 首先針對空的型別容器, 直接將 Result 作為回傳型別, 因為 Result 必定是這一類型別中 sizeof 值最大的那個, 再大就沒有內建型別能夠匹配了. 除此之外, 這還是超函式結束迴圈的標誌. 對於一般情況, 我們首先判斷給定的樣板引數 TResultsizeof 值是否相等. 如果相等的話, 直接回傳 Result 型別即可; 如果不想等的話, 看看型別 Tsizeof 值是否屬於 (sizeof(Result), sizeof(next_type)] 之間, 如果是的話就回傳 next_type, 如果不是就在剩餘的型別中繼續尋找

有了上述輔助的超函式, 我們就可以正式實作 std::make_unsignedstd::make_signed :

template <typename T>
struct make_signed {
    using type = copy_cv_t<T,
            typename make_signed_or_unsigned_auxiliary<remove_cv_t<T>, signed char, typename signed_integral::remaining>::type
    >;
};

template <typename T>
struct make_unsigned {
    using type = copy_cv_t<T,
            typename make_signed_or_unsigned_auxiliary<remove_cv_t<T>, unsigned char, typename unsigned_integral::remaining>::type
    >;
};

我們已經給出了型別集合中的第一個型別作為 Result 引數, 那麼只需要剩餘的集合即可, 當然將整個 signed_integral 或者 unsigned_integral 直接作為引數傳入也不會得到錯誤的回傳型別, 只是多了一次判斷而已. 最後要注意, 把 const 限定和 volatile 限定複製回去

有了這個思路, 接下來的 promotion 系列函式實作起來非常簡單 :

template <typename T, typename = void>
struct integral_promotion_auxiliary {
    static_assert(is_integral_v<T>, "This trait requires an integral type!");
};
template <typename T>
struct integral_promotion_auxiliary<T, enable_if_t<is_signed_v<T>>> {
    using type = conditional_t<
            is_same_v<typename promotion_index<T, signed_integral>::type::type, __int128_t>,
            __int128_t, typename promotion_index<T, signed_integral>::type::remaining::type>;
};
template <typename T>
struct integral_promotion_auxiliary<T, enable_if_t<is_unsigned_v<T>>> {
    using type = conditional_t<
            is_same_v<typename promotion_index<T, unsigned_integral>::type::type, __uint128_t>,
            __uint128_t, typename promotion_index<T, unsigned_integral>::type::remaining::type>;
};

template <typename T, typename = void>
struct floating_point_promotion_auxiliary {
    static_assert(is_floating_point_v<T>, "The trait requires a floating point type!");
};
template <typename T>
struct floating_point_promotion_auxiliary<T, enable_if_t<is_floating_point_v<T>>> {
    using type = conditional_t<
            is_same_v<typename promotion_index<T, floating_point>::type::type, long double>,
            long double, typename promotion_index<T, floating_point>::type::remaining::type>;
};

template <typename T, typename = void>
struct character_promotion_auxiliary {
    static_assert(is_character_v<T>, "The trait requires a character type!");
};
template <typename T>
struct character_promotion_auxiliary<T, enable_if_t<is_character_v<T>>> {
    using type = conditional_t<
            is_same_v<typename promotion_index<T, character>::type::type, char32_t>,
            char32_t, typename promotion_index<T, character>::type::remaining::type>;
};

template <typename T, typename = void>
struct arithmetic_promotion_auxiliary {
    static_assert(is_integral_v<T> or is_floating_point_v<T>,
            "The trait requires a character type or an integral type!");
};
template <typename T>
struct arithmetic_promotion_auxiliary<T, enable_if_t<is_integral_v<T>>> :
        copy_cv<T, typename integral_promotion_auxiliary<remove_cv_t<T>>::type> {};
template <typename T>
struct arithmetic_promotion_auxiliary<T, enable_if_t<is_floating_point_v<T>>> :
        copy_cv<T, typename floating_point_promotion_auxiliary<remove_cv_t<T>>::type> {};

這些是輔助性的超函式

template <typename T>
struct integral_promotion {
    using type = copy_cv_t<T, typename integral_promotion_auxiliary<remove_cv_t<T>>::type>;
};

template <typename T>
struct floating_point_promotion {
    using type = copy_cv_t<T, typename floating_point_promotion_auxiliary<remove_cv_t<T>>::type>;
};

template <typename T>
struct character_promotion {
    using type = copy_cv_t<T, typename character_promotion_auxiliary<remove_cv_t<T>>::type>;
};

template <typename T>
struct arithmetic_promotion : arithmetic_promotion_auxiliary<T> {};

具體的我就不再累贅, 大家可以自行理解

4. class_template_traits

這個超函式是用於萃取 T<Args...> 這樣的型別

template <typename ...>
struct class_template_traits;

template <template <typename ...> typename T, typename ...Args>
struct class_template_traits<T<Args...>> {
    using type = T<Args...>;
    template <typename ...Ts>
    using rebind = T<Ts...>;
    using arguments = type_container<Args...>;
};

5. std::conjunction, std::disjunctionstd::negation

這三個超函式實際上是一系列布林表達式的樣板超函式形式. 我們將 p_{1} \vee p_{2} \vee ... \vee p_{n} 稱為析取範式; 將 p_{1} \wedge p_{2} \wedge ... \wedge p_{n} 稱為合取範式; 將 \sim p 稱為否定. 用 C++ 的方法分別表示為 p_{1}\ \text {and}\ p_{2}\ \text {and}\ ... \text {and}\ p_{n}p_{1}\ \text {or}\ p_{2}\ \text {or}\ ... \text {or}\ p_{n}\text {not}\ p. 因此, 這三個超函式給定的引數必定是一系列 std::bool_constant :

template <typename BooleanExpression>
struct negation : bool_constant<not BooleanExpression::value> {};

template <typename ...>
struct conjunction : true_type {};
template <typename BooleanExpression>
struct conjunction<BooleanExpression> : BooleanExpression {};
template <typename BooleanExpressionLHS, typename BooleanExpressionRHS>
struct conjunction<BooleanExpressionLHS, BooleanExpressionRHS> :
        bool_constant<BooleanExpressionLHS::value and BooleanExpressionRHS::value> {};
template <typename BooleanExpressionLHS, typename BooleanExpressionRHS, typename ...BooleanExpressions>
struct conjunction<BooleanExpressionLHS, BooleanExpressionRHS, BooleanExpressions...> :
        conjunction<conjunction<BooleanExpressionLHS, BooleanExpressionRHS>, BooleanExpressions...> {};

template <typename ...>
struct disjunction : false_type {};
template <typename BooleanExpression>
struct disjunction<BooleanExpression> : BooleanExpression {};
template <typename BooleanExpressionLHS, typename BooleanExpressionRHS>
struct disjunction<BooleanExpressionLHS, BooleanExpressionRHS> :
        bool_constant<BooleanExpressionLHS::value or BooleanExpressionRHS::value> {};
template <typename BooleanExpressionLHS, typename BooleanExpressionRHS, typename ...BooleanExpressions>
struct disjunction<BooleanExpressionLHS, BooleanExpressionRHS, BooleanExpressions...> :
        disjunction<disjunction<BooleanExpressionLHS, BooleanExpressionRHS>, BooleanExpressions...> {};

6. std::common_type

std::common_type 接受一系列的樣板引數, 用於確定這些樣板引數之間是否存在共用型別, 即是否存在某一個型別使得所有型別都可以向其發生隱含型別轉化. 如果存在, std::common_type 中會存在一個名稱為 type 的型別別名作為回傳型別; 否則, std::common_type 中不存在任何成員

這個超函式的實作非常簡單, 但是需要用到一個技巧 : true ? expr1 : expr2. 如若 expr1 對應的型別 T1expr2 對應的型別 T2 可以發生某種隱含型別轉化, 那麼該表達式的結果就是發生隱含型別轉化之後對應的型別 T; 如若他們無法發生隱含型別轉化, 則會產生編碼錯誤 :

template <typename ...>
struct common_type {};
template <typename ...Ts>
using common_type_t = typename common_type<Ts...>::type;
template <typename T>
struct common_type<T> {
    using type = common_type_t<T, T>;
};
template <typename T, typename U>
struct common_type<T, U> {
    using type = decltype(make_true<T, U> ? declval<decay_t<T>>() : declval<decay_t<U>>());
};
template <typename T, typename U, typename V, typename ...Ts>
struct common_type<T, U, V, Ts...> : common_type<common_type_t<T, U>, V, Ts...> {};

7. std::common_reference

這個超函式是在 C++ 20 引入 <type_traits> 的, 和 std::common_type 類似, 但是這個超函式是用於確定給定的一系列樣板引數中是否存在共用的參考型別

首先, 我們定義一個輔助的型別別名 :

template <typename T, typename U>
using common_ref_auxiliary = decltype(make_true<T, U> ? declval<T (&)()>()() : declval<U (&)()>()());

接下來, 針對 T &U &, T &&U &&, T &U&&, T &&U & 四種情況進行偏特製化 :

template <typename T, typename U, typename = void>
struct common_ref_impl {};
template <typename T, typename U>
using common_ref = typename common_ref_impl<T, U>::type;
template <typename T, typename U>
struct common_ref_impl<T &, U &, void_t<common_ref_auxiliary<
        add_lvalue_reference_t<copy_cv_t<T, U>>, add_lvalue_reference_t<copy_cv_t<U, T>>>>> {
    using type = common_ref_auxiliary<
            add_lvalue_reference_t<copy_cv_t<T, U>>, add_lvalue_reference_t<copy_cv_t<U, T>>>;
};
template <typename T, typename U>
using common_ref_make_rvalue_ref = add_rvalue_reference_t<remove_reference_t<common_ref<T &, U &>>>;
template <typename T, typename U>
struct common_ref_impl<T &&, U &&, enable_if_t<is_convertible_v<T &&, common_ref_make_rvalue_ref<T, U>>
        and is_convertible_v<U &&, common_ref_make_rvalue_ref<T, U>>>> {
    using type = common_ref_make_rvalue_ref<T, U>;
};
template <typename T, typename U>
using common_ref_for_const_ref = common_ref<const T &, U &>;
template <typename T, typename U>
struct common_ref_impl<T &&, U &, enable_if_t<is_convertible_v<T &&, common_ref_for_const_ref<T, U>>>> {
    using type = common_ref_for_const_ref<T, U>;
};
template <typename T, typename U>
struct common_ref_impl<T &, U &&> : common_ref_impl<U &&, T &> {};

上面同樣將 SFINAE 運用到了偏特製化中. 除此之外, 還需要針對類別樣板的情況實作輔助超函式 :

template <typename T, typename U, template <typename> typename TQual, template <typename> typename UQual>
struct basic_common_reference {};
template <typename T>
struct copy_ref_for_class_template {
    template <typename U>
    using type = copy_cv_t<T, U>;
};
template <typename T>
struct copy_ref_for_class_template<T &> {
    template <typename U>
    using type = add_lvalue_reference_t<copy_cv_t<T, U>>;
};
template <typename T>
struct copy_ref_for_class_template<T &&> {
    template <typename U>
    using type = add_rvalue_reference_t<copy_cv_t<T, U>>;
};
template <typename T, typename U>
using basic_common_ref = typename basic_common_reference<remove_cvref_t<T>, remove_cvref_t<U>,
        copy_ref_for_class_template<T>::template type, copy_ref_for_class_template<U>::template type>::type;

最後, 尋訪不同的情況

template <typename T, typename U, int Bullet = 1, typename = void>
struct common_reference_auxiliary : common_reference_auxiliary<T, U, Bullet + 1> {};
template <typename T, typename U>
struct common_reference_auxiliary<T &, U &, 1, void_t<common_ref<T &, U &>>> {
    using type = common_ref<T &, U &>;
};
template <typename T, typename U>
struct common_reference_auxiliary<T &&, U &&, 1, void_t<common_ref<T &, U &>>> {
    using type = common_ref<T &&, U &&>;
};
template <typename T, typename U>
struct common_reference_auxiliary<T &, U &&, 1, void_t<common_ref<T &, U &&>>> {
    using type = common_ref<T &, U &&>;
};
template <typename T, typename U>
struct common_reference_auxiliary<T &&, U &, 1, void_t<common_ref<T &&, U &>>> {
    using type = common_ref<T &&, U &>;
};
template <typename T, typename U>
struct common_reference_auxiliary<T, U, 2, void_t<basic_common_ref<T, U>>> {
    using type = basic_common_ref<T, U>;
};
template <typename T, typename U>
struct common_reference_auxiliary<T, U, 3, void_t<common_ref_auxiliary<T, U>>> {
    using type = common_ref_auxiliary<T, U>;
};
template <typename T, typename U>
struct common_reference_auxiliary<T, U, 4, void_t<typename common_type<T, U>::type>> {
    using type = typename common_type<T, U>::type;
};
template <typename T, typename U>
struct common_reference_auxiliary<T, U, 5, void> {};

就可以獲得最終的型別輸出結果 :

template <typename ...>
struct common_reference {};
template <typename ...Ts>
using common_reference_t = typename common_reference<Ts...>::type;
template <typename T>
struct common_reference<T> {
    using type = T;
};
template <typename T, typename U>
struct common_reference<T, U> : common_reference_auxiliary<T, U> {};
template <typename T, typename U, typename ...Ts>
struct common_reference<T, U, Ts...> : common_reference<common_reference_t<T, U>, Ts...> {};

8. std::aligned_storage, std::aligned_uniontype_with_alignment

std::aligned_storage 適用於獲得一個能夠存儲 POD 型別的記憶體佈局 :

#include <iostream>

using namespace std;
struct A {
    int a;
    A(int a, int b) : a {(a + b) / 2} {}
};
int main(int argc, char *argv[]) {
    using A_POD = aligned_storage_t<sizeof(A), alignof(A)>;
    A_POD a, b;
    new (&a) A(10, 20);
    b = a;
    cout << reinterpret_cast<A &>(b).a << endl;
}

其實作方式和 std::make_unsignedstd::make_signed 差不多, 不過它是尋找最大的對齊 :

struct aligned_storage_double_helper {
    long double _1;
};
struct aligned_storage_double4_helper {
    double _1[4];
};
using aligned_storage_helper_types = type_container<unsigned char, unsigned short, unsigned int,
        unsigned long, unsigned long long, double, long double, aligned_storage_double_helper,
        aligned_storage_double4_helper, int *>;
template <size_t Align>
struct alignas(Align) overaligned {};
template <typename, size_t>
struct aligned_storage_find_suitable_type;
template <size_t Align, typename T, typename ...Args>
struct aligned_storage_find_suitable_type<type_container<T, Args...>, Align> {
    using type = conditional_t<Align == alignof(T), T,
            typename aligned_storage_find_suitable_type<type_container<Args...>, Align>::type>;
};
template <size_t Align>
struct aligned_storage_find_suitable_type<type_container<>, Align> {
    using type = overaligned<Align>;
};
template <size_t Length, size_t Align1, size_t Align2>
struct aligned_storage_selector {
private:
    constexpr static auto min {Align1 < Align2 ? Align1 : Align2};
    constexpr static auto max {Align1 < Align2 ? Align2 : Align1};
public:
    constexpr static auto value {Length < max ? min : max};
};
template <typename, size_t>
struct aligned_storage_find_max_align;
template <size_t Length, typename T>
struct aligned_storage_find_max_align<type_container<T>, Length> : alignment_of<T> {};
template <size_t Length, typename T, typename ...Args>
struct aligned_storage_find_max_align<type_container<T, Args...>, Length> : constant<size_t,
        aligned_storage_selector<Length, alignof(T),
        aligned_storage_find_max_align<type_container<Args...>, Length>::value>::value> {};

上面都是實作 std::aligned_storage 的輔助性超函式

template <size_t Length, size_t Align =
        aligned_storage_find_max_align<aligned_storage_helper_types, Length>::value>
struct aligned_storage {
private:
    using aligner = typename aligned_storage_find_suitable_type<
            aligned_storage_helper_types, Align>::type;
public:
    union type {
         aligner align;
         unsigned char data[(Length + Align - 1) / Align * Align];
    };
};

std::aligned_unionstd::aligned_storage 差不多, 但是用於存儲等位的資料

#include <iostream>

using namespace std;
union U {
    int i;
    char c;
    double d;
    U(const char *str) : c {str[0]} {}
};
int main(int argc, char *argv[]) {
    using U_POD = aligned_union_t<sizeof(U), int, char, double>;
    U_POD a, b;
    new (&a) U("hello world!");
    b = a;
    cout << reinterpret_cast<U &>(b).i << endl;
}

實作 std::aligned_union 需要使用的輔助性超函式有 :

template <typename, typename ...>
struct aligned_union_find_max_align;
template <typename T>
struct aligned_union_find_max_align<T> : alignment_of<T> {};
template <typename T, typename U>
struct aligned_union_find_max_align<T, U> :
        conditional<alignof(T) < alignof(U), alignment_of<U>, alignment_of<T>> {};
template <typename T, typename U, typename ...Args>
struct aligned_union_find_max_align<T, U, Args...> : conditional_t<alignof(T) < alignof(U),
        aligned_union_find_max_align<U, Args...>, aligned_union_find_max_align<T, Args...>> {};
template <size_t, size_t ...>
struct aligned_union_find_max_number;
template <size_t N>
struct aligned_union_find_max_number<N> : constant<size_t, N> {};
template <size_t N, size_t M>
struct aligned_union_find_max_number<N, M> :
        conditional_t<N < M, constant<size_t, M>, constant<size_t, M>> {};
template <size_t N, size_t M, size_t ...Numbers>
struct aligned_union_find_max_number<N, M, Numbers...> :
        aligned_union_find_max_number<aligned_union_find_max_number<N, M>::value, Numbers...> {};

接下裡實作 std::aligned_union :

template <size_t Length, typename ...Ts>
struct aligned_union {
    struct type {
        alignas(aligned_union_find_max_align<Ts...>::value)
        char data[aligned_union_find_max_number<Length, sizeof...(Ts)>::value];
    };
};

type_with_alignment 用於獲得一個對齊方式是指定 POD 型別指定倍數的型別. 實作它需要用到的輔助性超函式有 :

union type_with_alignment_max_alignment {
    char _1;
    short _2;
    int _3;
    long _4;
    long long _5;
    __int128_t _6;
    float _7;
    double _8;
    long double _9;
};
using type_with_alignment_helper_types = type_container<
        char, short, int, long, long long, double, long double>;
template <typename, size_t>
struct type_with_alignment_find_suitable_type;
template <size_t Align>
struct type_with_alignment_find_suitable_type<type_container<>, Align> {
    using type = type_with_alignment_max_alignment;
};
template <size_t Align, typename T, typename ...Args>
struct type_with_alignment_find_suitable_type<type_container<T, Args...>, Align> {
    using type = conditional_t<Align < alignof(T), T,
    typename type_with_alignment_find_suitable_type<type_container<Args...>, Align>::type>;
};

接下來實作 type_with_alignment :

template <size_t Align>
struct type_with_alignment : type_with_alignment_find_suitable_type<type_with_alignment_helper_types, Align> {};

9. std::has_unique_object_representations

std::has_unique_object_representations 是 C++ 17 引入 <type_traits> 的, 其接受一個樣板引數 T. 要明白這個型別萃取怎麼使用, 我需要從源頭開始說起. 一個是型別的值表達, 另外一個是型別的物件表達. 所謂一個型別 T 的值表達是一個 unsigned char 陣列對應的佔位, 陣列大小等同於 sizoef(T); 所謂一個型別 T 的物件表達是持有型別 T 的位元集合. 若型別 T 的複製操作是 trivial 的, 那麼顯然值表達是物件表達中確定值的一組位元集合. 對於類別

struct s {
    char c;
    int i;
};

一般來說, sizeof(s) 的值為 8. 根據記憶體對位, 編碼器要在變數 c 後面插入三個位元組, 這三個位元組是物件存儲的一部分, 也就是物件表達的一部分. 但是你更改這三個位元組的值, 通常並不會影響 s 的值 :

inline bool operator==(const s &lhs, const s &rhs) noexcept {
    return lhs.c == rhs.c and lhs.i == rhs.i;
}

但是如果這樣去寫 :

inline bool operator==(const s &lhs, const s &rhs) noexcept {
    return std::memcmp(&lhs, &rhs, sizeof lhs) == 0;
}

結果就不太一樣了. 因為 std::memcmp 是通過逐個位元進行比較的, 這其中也會包含那三個原本應該空白的位元組的部分. 然而對於類別

struct s2 {
    int c;
    int i;
};

來說, 就不會存在這樣的問題

對於 std::has_unique_object_representations, 若

  • T 的複製操作是 trivial 的
  • T 的任意兩個相同物件具有相同的物件表達

那麼, std::has_unique_object_representations 的回傳型別為 std::true_type, 否則為 std::false_type

那麼我們什麼時候會使用 std::has_unique_object_representations 呢? 考慮逐個位元組甚至逐個位元進行雜湊的情況

實作 std::has_unique_object_representations 需要用到編碼器魔法, 一般為 __has_unique_object_representations

10. std::is_pointer_interconvertible_with_class, std::is_pointer_interconvertible_base_of, std::is_corresponding_memberstd::is_constant_evaluatedstd::is_layout_compatible

這幾個特性萃取都是 C++ 20 引入的, 而且部分特性都有一個特徵, 那就是它們都是被 constexpr 標識的函式, 而不是樣板類別

std::is_pointer_interconvertible_with_class 是確定對於 T Class::*, 其要存取的成員變數是否屬於 Class, 如果屬於, 那麼回傳 true; 否則, 回傳 false. 其函式宣告為 :

template<class S, class M>
constexpr bool is_pointer_interconvertible_with_class(M S::*mp) noexcept;

另外, 如果 S 不是標準佈局, M 不是物件的型別又或者傳入的指標為空, 那麼其永遠回傳 false. 對於它的用法, 我舉個實例 :

#include <iostream>

using namespace std;
template<class S, class M>
constexpr bool is_pointer_interconvertible_with_class(M S::* mp) noexcept;
struct A {
    int a;
};
struct B {
    int b;
};
struct C : A, B {};
int main(int argc, char *argv[]) {
    cout << is_pointer_interconvertible_with_class(&C::a) << endl;      //輸出結果 : true, &C::a 的型別是 int A::*, a 是類別 A 的成員變數
    cout << is_pointer_interconvertible_with_class<B>(&C::a) << endl;      //輸出結果 : false, &C::a 的型別是 int A::*, a 是類別 B 的成員變數
}

雖然 Clang 自帶的 libc++ 標準樣板程式庫沒有引入這個函式. 不過我們自己實作的話, 實作方式大致是

template <typename T, typename Class>
inline constexpr bool is_pointer_interconvertible_with_class(T Class::*mp) noexcept {
    return __builtin_is_pointer_interconvertible_with_class(mp);
    //return __libcpp_is_pointer_interconvertible_with_class(mp);
}

這也是編碼器魔法的一種, 只不過這是函式形式的罷了, 我們自己並不能通過語言層面進行實作. 接下來的這幾個函式實作方式大致都是相同的

std::is_pointer_interconvertible_base_of 是一個類別樣板, 它接受兩個樣板引數 BaseDerived, 用於判斷型別 Derived 是否是無歧義地從基礎類別 Base 衍生 (reinterpret_cast<Base &>(declval<Derived &>()) 必須合法且有結果), 且每個 Derived 物件與其 Base 子物件的指標可以相互轉化. 從目前網路上的資料來看, 我只能寫出一個宣告 :

template <typename Base, typename Derived>
struct is_pointer_interconvertible_base_of;

並不能寫出太好的實作

std::is_corresponding_member 同樣是一個函式, 其宣告為

template<class S1, class S2, class M1, class M2>
constexpr bool is_corresponding_member(M1 S1::*mp, M2 S2::*mq) noexcept;

要說明它的用途, 必須介紹一個名為公共起始序列的概念. 簡單地來說, 對於類別 S1S2, 將其放入等位中 :

union U {
    struct S1 {
        T1 a;
        T2 b;
        T3 c;
        //...
    };
    struct S2 {
        U1 a;
        U2 b;
        U3 c;
        //...
    };
};

如果存取 U.S1.a 和 存取 U.S2.a 相同, 那麼我們說 U.S1.a 和 U.S2.a 位於類別 S1S2 的公共序列部分. 對於公共起始序列, 是公共序列部分的頭部. 那麼此時, std::is_corresponding_member 的用處就很清楚了 : 它用於確定 mpmq 是否指代 S1S2 的公共起始序列中的對應成員

std::is_constant_evaluated 同樣是一個函式, 其宣告為

constexpr bool is_constant_evaluated() noexcept;

它用於檢查一個表達式是否可以在編碼期進行計算 :

constexpr void f(int a, char b) {
    if(is_constant_evaluated() and (a + b) < 65535) {
        //constexpr friendly code here
    }else {
        //runtime friendly code here
    }
}

std::is_layout_compatible 是一個類別樣板, 其宣告為

template<class T, class U>
struct is_layout_compatible;

它用於檢查型別 TU 是否為佈局相容的. 根據佈局相關的特性, 相信這個函式也是要通過編碼器魔法來實作

 

 

如果你可以看完這兩篇文章, 並且可以跟隨這兩篇文章寫一個自己的 <type_traits>, 那麼相信你的樣板超編程能力將有所提升. 不過遺憾的是, 由於學業原因, 這個系列到這裡會暫時停止更新. 更多的進階樣板超編程教學可能會在一到兩年後才重新開始更新

這裡附上我實作的 type_traits.hpp : https://github.com/Jonny0201/data_structure/blob/master/data_structure/type_traits.hpp