C++中的模板元編程


目錄

概述

模板元編程可以說是C++中最困難也是最強大的編程范式。模版元編程不同於普通的運行期程序,它執行完全是在編譯期,並且它操縱的數據不能是運行時變量,只能是編譯期常量(且不可修改)。因此,模版元編程需要很多技巧,在這里你會遇到非常多的枚舉常量、繼承、模板偏特化、可變模板參數、類型萃取等方法,如果要讀懂STL庫,那么模板元編程必須要掌握;不僅如此,C++中的許多黑科技都是依賴於模板元編程的,例如我們甚至可以寫出編譯期排序的算法

模板元編程:template meta programing

模板元編程又兩個部分組成:元數據和元函數。元數據是指編譯期能在編譯期處理的數據,即編譯期常量;元函數指操縱元數據的函數,在編譯期調用,它通常表現為一個模板類或一個模板函數

模板元編程十分特殊,它無法使用if-elseforwhile這些運行期語句。但是我們可以通過std::enable_ifstd::integer_sequence等元函數來實現各種操作

type_traits-定義元數據

<type_traits>是C++11提供的模板元基礎庫,它提供了模板元編程中需要的常用的基礎元函數

std::integral_constant,定義編譯期常量

舉個例子,C++11中提供了std::integral_constant來定義編譯期常量

template<typename T>
using one_constant = std::integral_constant<T, 1>;

template<typename T>
struct one_struct : std::integral_constant<T, 1> {};

因此我們可以使用one_constant<int>::valueone_struct<int>::value來獲取編譯期常量int 1

而在C++11之前,我們定義這個常量就需要用到enum或static-const

struct one_struct
{
    enum { value = 1 };
};
struct one_struct
{
    static const int value = 1;  
};

然后通過one_struct::value來訪問值,其中enum能隱式轉換為int

std::integral_constant的實現也非常的簡單

template <class _Ty, _Ty _Val>
struct integral_constant {
    static constexpr _Ty value = _Val;

    using value_type = _Ty;
    using type       = integral_constant;

    constexpr operator value_type() const noexcept {
        return value;
    }

    _NODISCARD constexpr value_type operator()() const noexcept {
        return value;
    }
};

可以看到,通過C++11的<type_traits>提供的一個簡單的std::integral_constant就可以很方便的定義編譯期常量,而無需再去使用enum和static-const。緊接着,庫中又提供了編譯期常量的bool類型

template <bool _Val>
using bool_constant = integral_constant<bool, _Val>;

using true_type  = bool_constant<true>;
using false_type = bool_constant<false>;

std::integer_sequence

std::is_integral_v,判斷是否為整形

std::is_integral_v,根據名稱可以看出它可以用於判斷T是否為integral類型,從_v可以得出它是一個元數據。std::is_integral_v在對T去除cv限定符后,在一堆integral類型中進行匹配比較,最后萃取出一個bool編譯期常量

// STRUCT TEMPLATE is_integral
template <class _Ty>
inline constexpr bool is_integral_v = _Is_any_of_v<remove_cv_t<_Ty>, bool, char, signed char, unsigned char,
    wchar_t,
#ifdef __cpp_char8_t
    char8_t,
#endif // __cpp_char8_t
    char16_t, char32_t, short, unsigned short, int, unsigned int, long, unsigned long, long long, unsigned long long>;

那么僅僅有一個“值”是不夠的,我們還需要一個類型,那就是std::is_integral,這里從“值”到“類型”的“逆”轉換很巧妙

為什么說是“逆”轉換呢,因為這個類的聲明順序是現有std::is_integral_v,后有std::is_integral

template <class _Ty>
struct is_integral : bool_constant<is_integral_v<_Ty>> {};
// true 兩者都派生自std::bool_constant<false>
std::cout << std::boolalpha << std::is_base_of_v<std::false_type, std::is_integral<std::string>> << std::endl;
// true 因為long long是整形類型
std::cout << std::boolalpha << std::is_integral_v<long long> << std::endl;

std::integer_sequence,定義編譯期整數序列

為什么說是整形呢,請看源碼。如果不是整形類型那么IDE和編譯期會給出靜態斷言報錯

// 一個類型加上一個非類型模板參數包
template <class _Ty, _Ty... _Vals>
struct integer_sequence { // sequence of integer parameters
    static_assert(is_integral_v<_Ty>, "integer_sequence<T, I...> requires T to be an integral type.");

    using value_type = _Ty;

    static constexpr size_t size() noexcept {
        return sizeof...(_Vals);
    }
};

來通過一個簡單的例子來了解它

template<typename T, T... Is>
void print_sequence(std::integer_sequence<T, Is...> sequence)
{
    ((std::cout << sequence.size() << "elements: ") << ... << Is);
}

int main()
{
    // 6 elements: 4328910
    print_sequence(std::integer_sequence<unsigned int, 4, 3, 2, 8, 9, 10>());
    // 10 elements: 0123456789
    print_sequence(std::make_integer_sequence<int, 10>());
    // 10 elements: 0123456789
    print_sequence(std::make_index_sequence<10>());
}

其他的相關源碼為

// 構建一個std::integer_sequence
// ALIAS TEMPLATE make_integer_sequence
template <class _Ty, _Ty _Size>
using make_integer_sequence = __make_integer_seq<integer_sequence, _Ty, _Size>;

template <size_t... _Vals>
using index_sequence = integer_sequence<size_t, _Vals...>;

// 構建一個std::integer_sequence<std::size_t>
template <size_t _Size>
using make_index_sequence = make_integer_sequence<size_t, _Size>;

template <class... _Types>
using index_sequence_for = make_index_sequence<sizeof...(_Types)>;
  • std::make_integer_sequence能創建一個0-_Size - 1的序列,需要注意的是,它並不是一個可調用的函數,而是一個type alias(模板別名)

  • std::index_sequencestd::size_t類型的std::integer_sequence

  • std::index_sequence_for將以參數包的個數作為序列的_Size,創建一個0-_Size - 1的序列

    // 5 elements: 01234
    print_sequence(std::index_sequence_for<float, std::iostream, char, double, std::shared_ptr<std::string>>{});	// 參數包大小為5
    

利用編譯期序列實現數組到元組的轉換

這是一個cppreference上的例子

template<typename Array, std::size_t... Is>
auto array_to_tuple_impl(const Array& _array, std::index_sequence<Is...> _sequence)
{
    return std::make_tuple(_array[Is]...);
}

template<typename T, std::size_t I, typename Sequence = std::make_index_sequence<I>>
auto array_to_tuple(const std::array<T, I> _array)
{
    return array_to_tuple_impl(_array, Sequence{});
}

int main()
{
    std::array<int, 3> a{12, 3, 2};

    auto full_t = array_to_tuple(a);
    auto part_t = array_to_tuple<int, 3, std::index_sequence<1, 2>>(a);
    
    // 32
    std::apply([](auto&&... obj) { ((std::cout << obj << " "), ...); }, full_t);
}

但是這個輸出std::tuple的方法並不完美,它會在輸出的末尾添加一個不必要的" ",所以在此之上進行一層編譯期序列的封裝,能夠判斷此時解析到元組的第幾個元素

template<typename Tuple, std::size_t... Is>
std::ostream& print_tuple_impl(std::ostream& os, const Tuple& tuple, std::index_sequence<Is...> sequence)
{
    return ((os << (Is == 0 ? "" : " ") << std::get<Is>(tuple)), ...);
}

template<typename... Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> tuple)
{
    return print_tuple_impl(os, tuple, std::index_sequence_for<Ts...>{});
    // return print_tuple_impl(os, tuple, std::make_index_sequence<sizeof...(Ts)>{});
}
// perfect print
std::cout << full_t << std::endl;	// 12 3 2

type_traits-判斷元數據

std::is_array,判斷是否數組類型

源碼十分簡單,根據特化類型來判斷數組類型

// TYPE PREDICATES
template <class>
inline constexpr bool is_array_v = false; // determine whether type argument is an array

template <class _Ty, size_t _Nx>
inline constexpr bool is_array_v<_Ty[_Nx]> = true;

template <class _Ty>
inline constexpr bool is_array_v<_Ty[]> = true;

// STRUCT TEMPLATE is_array
template <class _Ty>
struct is_array : bool_constant<is_array_v<_Ty>> {};

測試代碼

float arr[10]{};
// true
std::cout << std::boolalpha << std::is_array_v<decltype(arr)> << std::endl;
// C++20 arr是一個有邊界的數組 故輸出false
std::cout << std::boolalpha << std::is_unbounded_array_v<decltype(arr)> << std::endl;
// false
std::cout << std::boolalpha << std::is_array_v<std::array<int, 10>> << std::endl;

std::is_same,判斷兩種類型是否相同

template <class, class>
inline constexpr bool is_same_v = false; // determine whether arguments are the same type
template <class _Ty>
inline constexpr bool is_same_v<_Ty, _Ty> = true;

template <class _Ty1, class _Ty2>
struct is_same : bool_constant<is_same_v<_Ty1, _Ty2>> {};

測試代碼

// 一般情況int都占4B true
std::cout << std::boolalpha << std::is_same_v<int, std::int32_t> << std::endl;
// true
std::cout << std::boolalpha << std::is_same_v<int, signed int> << std::endl;

更多類型判斷

更多內容可以查看文檔:cppreference-類型屬性

type_traits-類型修改

根據上文中的std::is_array,我們可以看到原理是對不同的類型參數做出不同的特化。現在我們還可以利用特化做點別的事情

std::remove_const,移除類型的const屬性

// STRUCT TEMPLATE remove_const
template <class _Ty>
struct remove_const { // remove top-level const qualifier
    using type = _Ty;
};

template <class _Ty>
struct remove_const<const _Ty> {
    using type = _Ty;
};

template <class _Ty>
using remove_const_t = typename remove_const<_Ty>::type;

測試代碼為

std::remove_const_t<const int> data = 100;
data = 10;

std::decay,朽化

std::is_const,判斷此類型是否是const限定的

std::is_const的實現是

// STRUCT TEMPLATE is_const
template <class>
inline constexpr bool is_const_v = false; // determine whether type argument is const qualified

template <class _Ty>
inline constexpr bool is_const_v<const _Ty> = true;
// true
std::cout << std::boolalpha << std::is_const_v<const int> << std::endl;
// false
std::cout << std::boolalpha << std::is_const_v<const int&> << std::endl;
// false
std::cout << std::boolalpha << std::is_const_v<const int(std::string, double)> << std::endl;
// false 因為是底層指針 指針本身是可以被修改的
std::cout << std::boolalpha << std::is_const_v<const int*> << std::endl;
// true 因為是頂層指針 指向的指向不能更改
std::cout << std::boolalpha << std::is_const_v<int* const> << std::endl;

這一句話很重要

determine whether type argument is const qualified

const int&為例,在調用到is_const_v<T>時,T被推導為intconst intconst int&不符,因此調用不到true版本的特化,所以結果為false

因此當一個類型的const修飾無意義並且它不是引用類型的時候,它是函數類型,如<int(std::string, double)>

std::is_function,判斷是否為函數類型

// STRUCT TEMPLATE is_function
template <class _Ty>
inline constexpr bool is_function_v = // only function types and reference types can't be const qualified
    !is_const_v<const _Ty> && !is_reference_v<_Ty>;

template <class _Ty>
struct is_function : bool_constant<is_function_v<_Ty>> {};

這一句話很重要

only function types and reference types can't be const qualified

std::remove_extent,移除一層數組

如果是一維數組,那么獲取它的元素類型;如果是高維數組,那么移除第一層數組

// true
std::cout << std::boolalpha << std::is_same_v<std::remove_extent_t<const int[]>, const int> << std::endl;
// true
std::cout << std::boolalpha << std::is_same_v<std::remove_extent_t<int[][10]>, int[10]><< std::endl;

朽化的具體步驟

對於最終的結果typestd::decay做了很多工作

// STRUCT TEMPLATE decay
template <class _Ty>
struct decay { // determines decayed version of _Ty
    using _Ty1 = remove_reference_t<_Ty>;
    using _Ty2 = typename _Select<is_function_v<_Ty1>>::template _Apply<add_pointer<_Ty1>, remove_cv<_Ty1>>;
    using type = typename _Select<is_array_v<_Ty1>>::template _Apply<add_pointer<remove_extent_t<_Ty1>>, _Ty2>::type;
};

template <class _Ty>
using decay_t = typename decay<_Ty>::type;
  • 首先移除類型的引用類型,記為_Ty1
  • 如果_Ty1是函數類型,那么在_Ty1的基礎上加一層指針,記為_Ty2,否則_Ty2為移除cv限定符的_Ty1
  • 如果_Ty1是數組類型,那么記type為移除一層數組且加上一層指針的_Ty1,否則記type_Ty2

例子

std::decay_t<int> A;           // int
std::decay_t<int&> B;          // int
std::decay_t<int&&> C;         // int
std::decay_t<const int&> D;    // int
std::decay_t<int[10]> E;        // int*
std::decay_t<int(int, int)> F;	// int(*)(int, int)

std::_Add_reference,添加引用類型

這個元函數也有必要挑出來講一下,實現也是非常的巧妙

// STRUCT TEMPLATE _Add_reference
template <class _Ty, class = void>
struct _Add_reference { // add reference (non-referenceable type)
    using _Lvalue = _Ty;
    using _Rvalue = _Ty;
};

template <class _Ty>
struct _Add_reference<_Ty, void_t<_Ty&>> { // (referenceable type)
    using _Lvalue = _Ty&;
    using _Rvalue = _Ty&&;
};

重點是這個void_t<_Ty&>。首先。void類型是無法添加左值引用或右值引用的,如void&void&&都會導致編譯錯誤。因此void_t<_Ty&>其實實現了一個substitution failure,對於_Add_reference<void>來說,並不會導致報錯,再“添加”了引用后void仍然是void

The major difference to directly using T& is that std::add_lvalue_reference<void\>::type is void, while void& leads to a compilation error.

SOF-is-only-void-a-non-referenceable-type

更多類型修改

更多內容可以查看文檔:cppreference-類型屬性

編譯期判斷

上文中提到過,模板元編程中沒有if-else這些判斷語句,但是<type_traits>中提供了不少元函數來實現這些功能

std::conditional,三目運算符

源碼實現也非常的簡單,都是技巧

// STRUCT TEMPLATE conditional
template <bool _Test, class _Ty1, class _Ty2>
struct conditional { // Choose _Ty1 if _Test is true, and _Ty2 otherwise
    using type = _Ty1;
};

template <class _Ty1, class _Ty2>
struct conditional<false, _Ty1, _Ty2> {
    using type = _Ty2;
};

template <bool _Test, class _Ty1, class _Ty2>
using conditional_t = typename conditional<_Test, _Ty1, _Ty2>::type;
// 是不是感覺很眼熟
int a = (100 > 50) ? 100 : 50; 

using IntType = std::conditional_t<true, int, float>;
using FloatType = std::conditional_t<false, int, float>;

注意,該原函數計算的參數和結果都是類型

std::enable_if,if-true表達式

源碼也是非常的簡單

// STRUCT TEMPLATE enable_if
template <bool _Test, class _Ty = void>
struct enable_if {}; // no member "type" when !_Test

template <class _Ty>
struct enable_if<true, _Ty> { // type is _Ty for _Test
    using type = _Ty;
};

template <bool _Test, class _Ty = void>
using enable_if_t = typename enable_if<_Test, _Ty>::type;

它只實現了true時期的特化,而當條件為false時,std::enable_if為空類,試圖調用不存在的type將會導致編譯出錯

限制模板參數的參數類型

在C#中存在一個where關鍵字可以用來限制泛型的類型,C++中同樣可以實現這一點

在小學數學中,我們稱能被2整除的整數為偶數,聰明的讀者可能會認為應該這么寫

// 只有整形才有%運算符
template<typename T>
bool is_odd(std::enable_if_t<std::is_integral_v<T>, T> num)
{
    return num % 2 == 0;
}

但是這樣會導致一個問題,函數模板沒有辦法自動推導參數的類型

std::cout << std::boolalpha << is_odd<int>(20) << std::endl;

所以我們應該把類型判斷放到返回值中

template<typename T>
std::enable_if_t<std::is_integral_v<T>, bool> is_odd(T num)
{
    return num % 2 == 0;
}
std::cout << std::boolalpha << is_odd(20) << std::endl;
// std::cout << std::boolalpha << is_odd(20.0) << std::endl;	// 編譯出錯

或者把它當作模板函數的匿名默認參數

template<typename T>
bool is_odd(T data, std::enable_if_t<std::is_integral_v<T>, void>* = nullptr)
{
    return data % 2 == 0;
}

匹配函數重載或偏特化類

SFINAE

SFINAE:Substitution failure is not an error,替代失敗不代表是個錯誤

知乎-SFINAE

  • error,在這里指編譯錯誤,當出現編譯錯誤的時候會停止代碼的鏈接等后續活動。如上述例子中判斷偶數的模板函數中,若函數參數非整形類型,那么會出現error

    std::cout << std::boolalpha << is_odd(20.0) << std::endl;
    
  • failure,當失敗發生時不代表編譯出錯,當我們使用switchif-else或是函數重載時,failure隨處可見,這里以函數重載為例

    void foo(std::size_t) {}
    void foo(std::string) {}
    
    foo("Jelly");
    foo((int*)nullptr);
    

    當我們執行第一個foo時,我們可以認為它對std::size_t的調用是一個failure,然后成功的調用到了std::string的版本

    當我們執行第二個foo時,我們可以認為它出現了兩次failure,並且兩個版本的函數都無法調用,這時出現了error,編譯器給出報錯

所以說,當我們遇到failure時,編譯器往往還會嘗試其他解決問題的方法,只有無路可走時才會出現error,這就是failure is not an error

所謂Substitution,就是將函數模板中的形參替換為實參的過程,來看一個例子,照搬自知乎

struct X {
	typedef int type1;
};

struct Y {
	typedef int type2;
};

// foo1
template<typename T> 
void foo(typename T::type1) {}

// foo2
template<typename T> 
void foo(typename T::type2) {}

// foo3
template<typename T> 
void foo(T) {}
int main()
{
	foo<X>(5);    // foo1: Succeed, foo1: Failed,  foo2: Failed
	foo<Y>(10);   // foo1: Failed,  foo1: Succeed, foo2: Failed
	foo<int>(15); // foo1: Failed,  foo1: Failed,  foo2: Succeed
}

很顯然,在調用foo<Y>(10)時,對於foo1而言實參明顯無法匹配到形參上,但我們此時並不能武斷的說發生了error,而應該認為發生了substitution failure,然后編譯期會嘗試下一個函數重載

利用SFINAE進行匹配

假設我們實現了一個接口和一個類,其中有increase的接口

struct ICounter 
{
	virtual void increase() = 0;
	virtual ~ICounter() {}
};

struct Counter : public ICounter 
{
	virtual void increase() override
	{
		// code...
	}
};

那么現在我想有一個函數,我希望它能夠同時處理整形類型和Counter的累加操作

template<typename T>
void increase_fuc(T& counter, std::enable_if_t<std::is_base_of_v<ICounter, T>, void>* = nullptr)
{
    counter.increase();
}

template<typename T>
void increase_fuc(T& num, std::enable_if_t<std::is_integral_v<T>, void>* = nullptr)
{
    ++num;
}
int data = 0;
Counter c;
increase_fuc(data);
increase_fuc(c);

利用Expression SFINAE進行匹配

你可能覺得上文中使用虛函數會有點消耗性能,又或者你不想使用接口

struct Counter
{
	void increase()
	{
		// code...
	}
};

C++11支持Expression SFINAE,讓我們可以不依賴於std::enable_if寫出函數重載,而使用decltype

template<typename T>
void increase_fuc(T& counter, std::decay_t<decltype(counter.increase())>* = nullptr)
{
    // 注意這里的decltype僅起到推斷作用 並不會真正執行
    counter.increase();
}

template<typename T>
void increase_fuc(T& num, std::decay_t<decltype(++num)>* = nullptr)
{
    // 只有支持++操作的類型才能調用到這個重載
    ++num;
}

由於int&*是非法的,因此需要加朽化修飾

// 那么對於int類型而言 在調用第一個重載時就發生了Expression Substitution Failure
int data = 0;
increase_fuc(data);

或者,你還可以這么寫來避免默認參數的使用,雖然說std::is_integral_v<T>的效果和 decltype(++)不同,非整形類型的也可能支持++操作,這里僅僅是給出個參考

template<typename T>
std::enable_if_t<std::is_integral_v<T>, void> increase_fuc(T& num)
{
    // 只有支持++操作的類型才能調用到這個重載
    ++num;
}
template<typename T>
auto increase_fuc(T& num) -> decltype(void(++num))
{
    // 只有支持++操作的類型才能調用到這個重載
    ++num;
}

使用C++17中的if-constexpr

template<typename T>
void increase_fuc(T& counter)
{
    if constexpr(std::is_base_of_v<ICounter, T>)
        counter.increase();
    else if (std::is_integral_v<T>)
        ++counter;
}

手撕環節

實現函數類型萃取類

struct PlaceHolderTag {};
struct FunctorTag {};
struct BuildInTypeTag {};

template<typename T, typename Holder = PlaceHolderTag>
struct function_traits;

template<typename T, typename... Ts>
struct function_traits<T(Ts...)> {
    using ReturnType = T;
    using FunctionType = T(Ts...);
    using FunctionPointer = T(*)(Ts...);

    template<std::size_t I>
    struct args
    {
        static_assert(I < sizeof...(Ts), "args index out of range");
        using ParamType = std::tuple_element_t<I, std::tuple<Ts...>>;
    };
    
    template<std::size_t I>
    using args_t = typename args<I>::ParamType;
};

// std::function類型
template<typename T, typename... Ts>
struct function_traits<std::function<T(Ts...)>> : public function_traits<T(Ts...)> {};

// 函數指針
template<typename T, typename... Ts>
struct function_traits<T(*)(Ts...)> : public function_traits<T(Ts...)> {};

// 成員函數
template<typename ClassType, typename T, typename...Ts>
struct function_traits<T (ClassType::*)(Ts...)> : public function_traits<T(Ts...)> {};

// 通過標簽分發內置類型與仿函數
template<typename T, typename Holder>
struct function_traits : public function_traits<T, std::conditional_t<std::is_class_v<T>, FunctorTag, BuildInTypeTag>> {};

// 內置類型
template<typename BuildInType>
struct function_traits<BuildInType, BuildInTypeTag>
{
    static_assert(std::_Always_false<BuildInType>, "this is just a build in type");
};

// 仿函數
template<typename ClassType>
struct function_traits<ClassType, FunctorTag> : public function_traits<decltype(&ClassType::operator())> {};
#define PRINT_TYPEID(T) std::cout << typeid(T).name() << std::endl

int main()
{
    std::function<int(std::string)> f;
    // int
    PRINT_TYPEID(function_traits<decltype(f)>::ReturnType);
    // 編譯出錯 "args index out of range"
    PRINT_TYPEID(function_traits<decltype(f)>::args_t<1>);
    // 編譯出錯 "this is just a build in type"
    PRINT_TYPEID(function_traits<int>);
}

但是對成員函數的重載並沒有這么簡單,對於常成員函數執行萃取操作,會導致編譯錯誤

struct Test_Functor
{
    void non_const_func(double) {}
    void const_func(int) const {}
};

int main()
{
    PRINT_TYPEID(function_traits<decltype(&Test_Functor::non_const_func)>::FunctionType);
    // 因為缺少對常函數的特化 所以其實會調用到內置類型的版本
    // PRINT_TYPEID(function_traits<decltype(&Test_Functor::const_func)>::ReturnType);
}

所以我們可以使用宏來減去模板的重復書寫,其中宏中的__VA_ARGS__代表參數包...

// 成員函數
#define MEMBER_FUNCTION_TRAITS(...) \
template<typename ClassType, typename T, typename...Ts> \
struct function_traits<T (ClassType::*)(Ts...) __VA_ARGS__> : public function_traits<T(Ts...)> {}; \

MEMBER_FUNCTION_TRAITS()
MEMBER_FUNCTION_TRAITS(const)
MEMBER_FUNCTION_TRAITS(volatile)
MEMBER_FUNCTION_TRAITS(const volatile)

實現std::variant

在C++17中,文檔中說道

The class template std::variant represents a type-safe union

但是std::variant的大小並不是數據包中的最大值,如果創建同類型的union,那么大小為8。因為std::variant自帶內存對齊

std::cout << sizeof(std::variant<int, bool, double>) << std::endl;	// 16

當我們想要實現自己的variant時,首先需要實現三個點

  • 在編譯期找出數據包中的最大值,以讓運行期能夠分配適量的空間
  • 賦值時需要根據原有的類型來調用析構函數,並調用placement new創建新的對象
  • 細節項(未實現):元素不能重復,自帶store以及get的類型檢查,檢查類型是否存在於參數包中

編譯期計算,兩個類型的最大者

template<auto Left, auto Right>
inline constexpr bool is_left_greater_than_right = Left > Right;

template<typename Left, typename Right>
struct binary_type_compare_max : std::conditional<is_left_greater_than_right<sizeof(Left), sizeof(Right)>, Left, Right> {};

template<typename Left, typename Right>
using binary_type_compare_max_t = typename binary_type_compare_max<Left, Right>::type;
template<typename  T>
inline void print_typeid() { std::cout << typeid(T).name() << std::endl; }

int main()
{
    print_typeid<binary_type_compare_max_t<int, double>>();
}

拓展,比較兩個數據的大小並返回較大的那個

template<auto Left, auto Right>
inline constexpr bool is_left_greater_than_right = Left > Right;

template<auto Left, auto Right>
struct binary_size_compare_max : std::conditional<is_left_greater_than_right<Left, Right>,
        std::integral_constant<std::size_t, Left>, std::integral_constant<std::size_t, Right>> {};

template<auto Left, auto Right>
inline constexpr auto binary_size_compare_max_v = binary_size_compare_max<Left, Right>::type::value;
int main()
{
    std::cout << binary_size_compare_max_v<10, 30> << std::endl;
}

編譯期計算,求一個類型參數包中size的最大者

template<typename... Ts>
struct multi_type_compare_max;

template<typename T, typename U, typename... Ts>
struct multi_type_compare_max<T, U, Ts...> : multi_type_compare_max<binary_type_compare_max_t<T, U>, Ts...> {};

template<typename T>
struct multi_type_compare_max<T> : std::integral_constant<std::size_t, sizeof(T)> {};

template<typename... Ts>
inline constexpr std::size_t multi_type_compare_max_v = multi_type_compare_max<Ts...>::value;
int main()
{
    // 最大size為8
    std::cout << multi_type_compare_max_v<int, double, std::int64_t, char> << std::endl;
}

編譯期計算,根據index求參數包中的類型

// 根據index獲取type
template<std::size_t index, typename...>
struct my_variant_element;

template<std::size_t index, typename T, typename... Ts>
struct my_variant_element<index, T, Ts...> : my_variant_element<index - 1, Ts...> {
    static_assert(index < sizeof...(Ts) + 1, "index out of range");
};

template<typename T, typename... Ts>
struct my_variant_element<0, T, Ts...> {
    using type = T;
};

template<std::size_t index, typename... Ts>
using my_variant_element_t = typename my_variant_element<index, Ts...>::type;
template<typename  T>
inline void print_typeid() { std::cout << typeid(T).name() << std::endl; }

int main()
{
    // char
    print_typeid<my_variant_element_t<2, int, double, char, bool>>();
}

編譯期計算,根據參數包中的類型求index

實現一:求差

// 根據type獲取index
template<typename, typename...>
struct my_variant_index;

template<typename Target, typename T, typename... Ts>
struct my_variant_index<Target, T, Ts...> : std::conditional_t<std::is_same_v<Target, T>,
        std::integral_constant<std::size_t, sizeof...(Ts)>, my_variant_index<Target, Ts...>> {};

// 匹配失敗
template<typename T>
struct my_variant_index<T> {
    static_assert(std::_Always_false<T>, "cant find T in args");
};

template<typename Target, typename... Ts>
inline constexpr std::size_t my_variant_index_v = sizeof...(Ts) - my_variant_index<Target, Ts...>::value - 1;
int main()
{
    // int位於參數包的第2位
    std::cout << my_variant_index_v<int, char, bool, int, long, long long, float, double> << std::endl;
}

實現二:正向遞增

// 根據type獲取index
template<std::size_t, typename, typename...>
struct my_variant_index_impl;

template<std::size_t index, typename Target, typename T, typename... Ts>
struct my_variant_index_impl<index, Target, T, Ts...> : std::conditional_t<std::is_same_v<Target, T>,
        std::integral_constant<std::size_t, index>, my_variant_index_impl<index + 1, Target, Ts...>> {};

// 匹配失敗
template<std::size_t index, typename T>
struct my_variant_index_impl<index, T> {
    static_assert(std::_Always_false<T>, "cant find T in args");
};

template<typename Target, typename... Ts>
inline constexpr std::size_t my_variant_index_v = my_variant_index_impl<0, Target, Ts...>::value;

template<typename Target, typename... Ts>
struct my_variant_index : std::integral_constant<std::size_t, my_variant_index_v<Target, Ts...>> {};

實現三:某大佬解法

運行期計算,析構內存上的數據

template<typename... Ts>
struct call_dtor_helper;

template<typename T, typename... Ts>
struct call_dtor_helper<T, Ts...> {
    void operator()(std::size_t index, void* memory)
    {
        if (index != 0)
            call_dtor_helper<Ts...>{}(index - 1, memory);
        else if (std::is_class_v<T>)
            reinterpret_cast<T*>(memory)->~T();
    }
};

template<>
struct call_dtor_helper<> {
    void operator()(std::size_t, void*) {}
};

my_variant

template<typename... Ts>
class my_variant
{
public:
    void* memory;
    std::size_t index;
public:
    my_variant() : memory(nullptr), index(0) {}

    template<typename T>
    my_variant(T&& data) : my_variant() {
        store(std::forward<T>(data));
    }

    ~my_variant() {
        if (memory == nullptr)
            return;
        call_dtor_helper<Ts...>{}(index, memory);
        ::operator delete(memory);
    }

    template<typename T>
    void store(T&& data) {
        // 無內存對齊
        if (memory == nullptr)
            memory = ::operator new(multi_type_compare_max_v<Ts...>);
        else
            call_dtor_helper<Ts...>{}(index, memory);
        ::new(memory) T(std::forward<T>(data));
        index = my_variant_index_v<T, Ts...>;
    }

    template<typename T>
    T& get() {
        return *reinterpret_cast<T*>(memory);
    }
};

測試代碼

盡管上文中寫的很多編譯期計算的代碼都用不上,但是以學習為目的我並沒有刪除它們。本次手撕環節實現的my_variant功能很弱雞,沒有考慮拷貝以及移動的問題,僅供學習參考

int main()
{
    my_variant<long long, NonCopyStruct_Str> v(4ll);
    std::cout << v.get<long long>() << std::endl;
    v.store<NonCopyStruct_Str>((std::string)"Jelly");
    std::cout << v.get<NonCopyStruct_Str>().name << std::endl;
}

更多

std::invoke_result,獲取返回值類型(C++17起)

struct TestResult {
    double operator()(char, int&) { return 2.0; }
    float operator()(int) { return 1.0; }
};

int main()
{
    std::cout << typeid(std::invoke_result_t<TestResult, char, int&>).name() << std::endl;	// double
    std::cout << typeid(std::invoke_result_t<TestResult, int>).name() << std::endl;	// float
    std::cout << typeid(decltype(TestResult()(20))).name() << std::endl;	// float
}

模板推導類型的舍棄

template<typename T>
struct print_t
{
    void print() { std::cout << std::boolalpha << "From T "   << std::is_same_v<T, const int&><< std::endl; }
};

template<typename T>
struct print_t<const T>
{
    void print() { std::cout << std::boolalpha << "From const T " << std::is_same_v<T, int&><< std::endl; }
};

template<typename T>
struct print_t<T&>
{
    void print() { std::cout << std::boolalpha << "From T& "  << std::is_same_v<T, const int><< std::endl; }
};

int main()
{
    // From T& true
    print_t<const int&>().print();
}

如果我們把<T&>的特化移除,那么

template<typename T>
struct print_t
{
    void print() { std::cout << std::boolalpha << "From T "   << std::is_same_v<T, const int&><< std::endl; }
};

template<typename T>
struct print_t<const T>
{
    void print() { std::cout << std::boolalpha << "From const T " << std::is_same_v<T, int&><< std::endl; }
};

int main()
{
    // From T true
    print_t<const int&>().print();
   	// From const T false
    print_t<const int>().print();
}

模板的底層實現是什么

知乎-C++ 模板在編譯過程上的實現原理是怎樣的?

學習資料

https://blog.csdn.net/jeffasd/article/details/84667090

https://ouuan.github.io/post/c-11-enable-if-的使用/

https://www.zhihu.com/column/c_1306966457508118528

https://zhuanlan.zhihu.com/p/378355217

https://www.cnblogs.com/qicosmos/p/4480460.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM