item 2:autoタイプの導出を理解する

6036 ワード

item 2:autoタイプの導出を理解する
item 1のテンプレートタイプ導出を読んだことがある場合は、autoタイプ導出に関するほとんどの知識を知っています.なぜなら、奇妙な場合を除いて、autoタイプ導出はtemplateタイプ導出と同じであるからです.でもどうしてこんなことになったの?templateタイプの導出はテンプレートと関数およびパラメータに関連するがautoはこれらのものを処理しなかった.
そうですが、大丈夫です.templateタイプからautoタイプへの導出には直接的なマッピング関係がある.ここではtemplateタイプ導出からautoタイプ導出に変換する直接アルゴリズムがある.
item 1ではtemplateタイプの導出は汎用関数テンプレートを解釈することである.
template<typename T>
void f(ParamType param);

このように簡単に呼び出します.
f(expr);

fの呼び出しに対して,コンパイラはexprを用いてTとParamTypeのタイプを導く.
1つの変数がautoで宣言されると、autoが演じている役割はtemplateのTに相当し、変数のタイプはParamTypeに相当する.直接例を挙げるともっと簡単です.
auto x = 27;

ここで、xのタイプはauto自身であり、一方、この声明では:
cosnt auto cx = x;

タイプはconst autoで、次のようになります.
cosnt auto& rx = x;

のタイプはconst auto&.x,cx,rxのタイプを導くために、コンパイラはここにtemplateの宣言があるように表現し、初期化式で対応するtemplateを呼び出します.
tamplate<typename T>        //    x template    
void func_for_x(T param);

func_for_x(27);             //      :paramType     
                            //     x   ,T       
                            //auto       

template<typename T>
void func_for_cx(const T param);

func_for_cx(x);

template<typename T>
void func_for_rx(cosnt T& param);

func_for_rx(x);

私が言ったように、autoのタイプ導出は、例外が1つしかありません(すぐに議論します)、他のものはtemplateのタイプ導出と同じです.
item 1はParamTypeの違いに基づいてtemplateのタイプを3つのケースに導いた.一方、autoを使用して変数を宣言すると、変数タイプがParamTypeに取って代わるため、ここでは3つのケースがあります.
  • の場合1:変数タイプはポインタまたは参照ですが、universal参照ではありません.
  • の場合2:変数タイプはuniversal参照です.
  • の場合3:変数タイプはポインタでも参照でもありません.

  • 状況1と状況3の例を見ました.
    auto x = 27;        //  3
    
    const auto cx = x;  //  3
    
    const auto& rx = x; //  1
    

    状況2をこのように想像することができます.
    auto&& uref1 = x;   //x int,     ,
                        //  uref1    int&
    
    auto&& uref2 = cx;  //cx cosnt int,     ,
                        //  uref2    const int&
    
    auto&& uref3 = 27;  //27 int,     ,
                        //  uref3     int&&
    

    item 1は、タイプがnon-referenceの場合、配列および関数がポインタに劣化する場合を議論し、autoタイプ導出においても発生する.
    const char name[] = "R. N. Brigs";
                        //name     const char[13]
    
    auto arr1 = name;   //arr1    const char*
    
    auto& arr2 = name;  //arr2    const char()[13]
    
    void someFunc(int, double); 
                        //      ,   void(int, double)
    
    auto func1 = someFunc;
                        //func1    void(*)(int,double)
    
    auto& func2 = someFunc;
                        //func2    void()(int, double)
    

    あなたが見たように、autoタイプガイドとtemplateタイプガイドは同じで、彼らは本質的にコインの両側です.
    一つの場合を除いて、彼らは違います.ある状況を観察してから、int変数を27で宣言したい場合は、C++98で2つの異なる構文を使用できます.
    int x1 = 27;
    int x2(27);
    

    C++11では、標準初期化のサポートにより、これらの操作が追加されました.
    int x3 = {27};
    int x4{27};
    

    要するに、4つの構文は、値27のint変数の結果を生成します.
    しかしitem 5の解釈によれば、変数を宣言する場合、正確なタイプをautoで置き換えるメリットはいくつかあるので、上記の変数宣言ではintをautoで置き換えるメリットがある.直接的なテキスト置換は、次のようなコードを生成します.
    auto x1 = 27;
    auto x2(27);
    auto x3 = {27};
    auto x4{27};
    

    これらの宣言はコンパイルできますが、autoで置き換えると、異なる解釈があります.最初の2つの文は、27のint変数を宣言しています.ただし、次の2つの文では、宣言された変数タイプは27のstd::initializer_です.list !
    auto x1 = 27;   //   int,   27
    
    auto x2(27);    //  
    
    auto x3 = {27}; //    std::initializer_list<int>
                    //  {27}
    
    auto x4{27};    //  
    

    このような結果はautoの特殊なタイプ導出規則に起因する.初期化リストの形式でauto宣言の変数を初期化すると、導出されるタイプはstd::initializer_である.List、次のコードは間違っています.
    auto x ={1, 2, 3.0};    //  !     
                            //std::initializer_list<T>  T
    

    注釈で述べたように、この場合のタイプ導出は失敗しますが、ここでは実際に2つのタイプ導出が発生していることを知る必要があります.1つはx 5からのautoタイプの導出である.x 5の初期化がカッコ内にあるため、x 5はstd::initializer_と導出される.list.でもstd::initializer_listはtemplateです.std::initializer_をTでインスタンス化するListはTのタイプも導き出さなければならないことを意味する.ここで発生した導出は第2のタイプ導出に属する:templateタイプ導出.この例では、初期化リストの値が同じタイプではないため、この導出は失敗した.
    autoタイプ導出とtemplateタイプ導出の唯一の違いは,初期化リストに対する異なるアプローチである.初期化リストでautoタイプの変数を宣言すると、導出されるタイプはstd::initializer_listのインスタンスです.ただし、同じ初期化リストがtemplateに入力されると、タイプの導出に失敗し、コードのコンパイルに失敗します.
    auto x = {11, 23, 9};   //   std::initializer_list<int>
    
    template<typename T> 
    void f(T param);
    
    f({11, 23, 9}); //  !    T   
    

    しかし、templateのパラメータがstd::initializer_であることを明確にするとList,template型導出はTを導出することに成功する:
    template<typename T>
    void f(std::initializer_list<T> initList);
    
    f({11, 23, 9})  //T    int,  initList    
                    //std::initializer_list<int>
    

    したがってautoとtemplateのタイプで導出される唯一の違いは、autoが初期化リストをstd::initializer_と仮定することである.listですが、templateタイプはそうしません.
    なぜautoタイプ導出が初期化リストにこのような特別なルールを持っているのか知りたいかもしれませんが、templateタイプ導出はありません.私も知りたいです.悲しいことに、私は納得できる説明を見つけられませんでした.しかし、ルールはルールであり、autoで変数を宣言し、初期化リストで初期化すると、std::initializer_が導出されることを覚えておく必要があります.list.特に、標準初期化を受け入れることを覚えておいてください.初期値は括弧の中で当たり前です.(It’s especially important to bear this in mind if you embrace the philosophy of uniform initialization—of enclosing initializing values in braces as a matter of course. ).C++11の典型的なエラーは、他の変数を宣言したい場合にstd::initializer_を意外に宣言することです.list変数.このトラップにより、一部の開発者は、必要に応じてカッコで変数を初期化する(item 7で議論する必要がある場合)
    C++11にとって物語は終わったが、C++14にとって物語は続く.C++14はautoを関数の戻り値として許可し、lambdas式ではautoをパラメータタイプとして使用することができる.しかしながら、autoのこれらの用法は、autoタイプ導出規則ではなくtemplateタイプ導出規則を用いる.したがって、autoタイプに初期化リストを返す関数の戻り値はコンパイルできません.
    auto createInitList()
    {
        return {1, 2, 3};   //  !    {1,2,3}   
    }
    

    C++14では、lambdas式のパラメータとして宣言されるautoタイプにも適用されます.
    std::vector<int> v;
    ...
    
    auto resetV = [&v](const auto& newValue)
                    { v = newValue; };
    
    ...
    
    resetV({1, 2, 3});  //  !    {1,2,3}   
    

    君が覚えていること
  • autoタイプ導出はtemplateタイプ導出と同様であることが多いが、autoタイプ導出は初期化リストがstd::initializer_であると仮定する.list、しかしtemplateタイプはそうしないと導いた
  • autoが関数としてタイプまたはlambdas式のパラメータタイプを返す場合、autoタイプ導出ルールではなくtemplateタイプ導出ルールがデフォルトで使用されます.