簡體   English   中英

了解C ++模板元編程

[英]Understanding C++ template metaprogramming

為了更好地理解C ++中的模板和元編程,我正在閱讀本文 ,但是我對代碼片段的理解很快就會消失,例如:

template<class A, template<class...> class B> struct mp_rename_impl;

template<template<class...> class A, class... T, template<class...> class B>
    struct mp_rename_impl<A<T...>, B>
{
    using type = B<T...>;
};

template<class A, template<class...> class B>
    using mp_rename = typename mp_rename_impl<A, B>::type;

代碼使用如下:

mp_rename<std::pair<int, float>, std::tuple>        // -> std::tuple<int, float>
mp_rename<mp_list<int, float>, std::pair>           // -> std::pair<int, float>
mp_rename<std::shared_ptr<int>, std::unique_ptr>    // -> std::unique_ptr<int>

有人可以像我這樣解釋代碼嗎? 我對非模板化C ++有一般性和基本的理解。

我沒有得到的是:

為什么用兩個類型參數( class A, template<class...> class B )聲明mp_rename_impl前向,然后它同時定義並專門設置[*]三個( template<class...> class A, class... T, template<class...> class B )和兩個( A<T...>, B )類型參數?

我理解它別名( using type = B<T...>;typeB<T...>而不是A<T...> ,但我真的不明白它是如何完成的。

另外,為什么A模板模板參數僅在專業化中?

[*]我肯定在這里弄錯了

為什么用兩個類型參數( class A, template<class...> class B )聲明mp_rename_impl前向,然后它同時定義並專門設置[*]三個( template<class...> class A, class... T, template<class...> class B )和兩個( A<T...>, B )類型參數?

forward聲明確定了實例化mp_rename_impl所需的參數數量,前者應該是實際類型,后者應該是模板。

然后當有一個實際的實例化時,它會嘗試匹配特化struct mp_rename_impl<A<T...>, B> ,並且這樣做可以考慮專業化的AT...B匹配的值的任意組合專業化的期望:即template<class...> class A, class... T, template<class...> class B 請注意,特化中的A參數與聲明中的A共享一個名稱,但不相同 - 前者是模板,后者是類型。 實際上,為了匹配特化,模板實例化必須作為聲明的A參數傳遞,並且模板的參數在T...處捕獲。 它對可以作為B傳遞的內容沒有新的限制(雖然using語句確實 - B<T...>需要有效或者你會得到編譯錯誤 - 對於SFINAE來說太晚了)。

另外,為什么A模板模板參數僅在專業化中?

專門化調用參數A ,但它在概念上與聲明中的A 相反,前者的A<T...>對應於后者A 也許專業化應該稱之為“TA”或其他東西來表明它是一個模板,從中可以結合T...參數形成實際的A 然后專門化為A<T...>, B ,因此編譯器從實際嘗試的任何實例化向后工作,以找到AT...B有效替換,由template<template<class...> class A, class... T, template<class...> class B>指定的形式的限制引導template<template<class...> class A, class... T, template<class...> class B>

這樣做可以確保專業化僅在兩個參數是已經給出一些參數類型的模板以及能夠獲取參數類型列表的模板時才匹配。 匹配過程有效地隔離了T類型列表,因此可以與B重用。

我的第一次嘗試不是你想要的,所以讓我簡單地試着回過頭來解釋一下你是六歲。

在函數具有原型和定義的意義上,它不是前向聲明的。 有一個實現任何A,並編譯為一個空結構(這是編譯器的唯一類型,但不需要任何實際的存儲或運行時代碼)。 然后,有第二個實現,僅適用於模板類A.

第二個定義中確實有兩個模板。 接下來是第二個定義它采用兩個參數A... T並將它們轉換為A<T>類型,它成為mp_rename_impl<A<T...>,B>的第一個參數。 所以它適用於任何A 模板類 但那是一種更具體的A 所以它是一個特殊化,需要在其范圍內聲明一個帶有類型定義的結構。 最后,第三個變體根本不是模板的特化。 它將模板化的mp_rename聲明為存儲在第二個聲明中每個結構范圍內的更復雜類型的別名,正如您所看到的那樣是范圍mp_rename_impl<A, B>的標識符type 信不信由你,這使他的模板代碼更具可讀性。

為什么頂部更通用的定義擴展為空結構? A不是模板類時,內容很簡單,但它確實需要某種類型的名稱,因此編譯器會認為它與其他所有類型都不同。 (在下面編寫我的示例以生成具有靜態常量作為成員而不是函數的類會更酷。實際上,我剛剛做了。)

更新為威脅使我的模板更像他的:

好的,模板元編程是一種編程,它不是讓程序在運行時計算某些東西,而是編譯器提前計算它並將答案存儲在程序中。 它通過編譯模板來實現。 運行起來要快得多,有時候! 但是你可以做什么是有限的。 主要是,你不能修改任何參數,你必須確保計算停止。

如果你在想,“你的意思是,就像功能性編程一樣?”你是一個非常聰明的五歲孩子。 您通常最終要做的是編寫具有基本案例的遞歸模板,這些基本案例可以擴展為展開的,簡化的代碼或常量。 這是一個例子,當您有三個或四個時,您可以從“計算機科學入門”課程中看到這個例子:

#include <iostream>

using std::cout;
using std::endl;

/* The recursive template to compute the ith fibonacci number.
 */
template < class T, unsigned i >
  struct tmp_fibo {
    static const T fibo = tmp_fibo<T,i-1>::fibo + tmp_fibo<T,i-2>::fibo;
  };

/* The base cases for i = 1 and i = 0.  Partial struct specialization
 * is allowed.
 */
template < class T >
  struct tmp_fibo<T,1U> {
    static const T fibo = (T)1;
  };

template < class T >
  struct tmp_fibo<T,0U> {
    static const T fibo = (T)0;
  };

int main(void) {
  cout << "fibo(50) = " << tmp_fibo<unsigned long long, 50>::fibo
       << ". fibo(10) = " << tmp_fibo<int, 10>::fibo << "."
       <<  endl;

  return 0;
}

編譯匯編語言,我們看到編譯器為行生成的代碼tmp_fibo<unsigned long long, 50>::fibo這里是完整的:

movabsq $12586269025, %rsi

模板在編譯時在每個結構內生成一個整數常量。 這些示例正在做什么,因為您可以在結構中聲明類型名稱,對類型執行相同的操作。

我會盡量簡單。 模板元編程是關於在編譯時計算類型的(您也可以計算值,但讓我們關注它)。

所以如果你有這個功能:

int f(int a, int b);

你有一個函數返回給定兩個int值的int值。

你這樣使用它:

int val = f(5, 8);

元功能對類型進行操作,而不是對值進行操作。 元函數看起來像這樣:

//The template parameters of the metafunction are the
//equivalent of the parameters of the function
template <class T, class U>
struct meta_f {
    typedef /*something here*/ type;
};

也就是說,元函數內部有嵌套type按照慣例 ,嵌套類型稱為type

所以你在非泛型上下文中調用這樣的元函數:

using my_new_type = meta_f<int, float>::type;

在通用上下文中,您必須使用typename

using my_new_type = typename meta_f<T, U>::type;

這返回一個類型,而不是運行時值,因為我們說元函數對類型進行操作。

標准庫中的元函數的示例可以在標題類型type_traits等中找到。 你有add_pointer<T>decay<T> 這些元函數返回給定類型的新類型。

在C ++ 14中,為了避免這樣的代碼片段,這些代碼很冗長:

using my_computed_type = typename std::add_pointer<T>::type;

創建了一些具有_t后綴的模板別名,再次按照慣例,直接為您調用元函數:

template <class T>
using add_pointer_t = typename std::add_pointer<T>::type;

現在你可以寫:

using my_computed_type = std::add_pointer_t<T>;

總而言之,在函數中,您將運行時值作為參數,在元函數中,參數是類型。 在使用通常語法調用的函數中,獲取運行時值。 在元函數中,您將獲得::type嵌套類型並獲取新的計算類型。

//Function invocation, a, b, c are values of type A, B, C
auto ret = f(a, b, c);

//Meta function invocation. A, B, C are types
using ret_t = typename meta_f<A, B, C>::type;

//Typical shortcut, equivalent to metafunction invocation.
using ret_t = meta_f_t<A,B,C>;

因此,對於第一個函數,您獲得一個值,對於其他函數,您獲得的是類型,而不是值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM