現代C++の理解decltype

6626 ワード

現代C++の理解decltype
現代C++の理解decltype
decltypeは変数名や式のタイプを生成するために使用され、その生成結果は明らかで、予測可能で、理解しやすく、理解しにくいものがあります.ほとんどの場合、decltypeは、テンプレートとautoを使用する場合のタイプ推定と比較して、変数名または式に作用するのは、変数名または式の正確なタイプを1回繰り返しただけです.
const int i = 0;                         // decltype(i)   const int
bool f(const Widget& w);                 // decltype(w)   const Widget&
                                         // decltype(f)   bool(const Widget&)
struct Point {
    int x, y;                            // decltype(Point::x)   int
};                                       // decltype(Point::y)   int
Widget w;                                // decltype(w)   Widget
if (f(w)) …                              // decltype(f(w))   bool

template                     //  std::vector      
class vector { 
public:
…
T& operator[](std::size_t index);
…
};
vector v;                           // decltype(v)   vector 
…
if (v[0] == 0) …                         // decltype(v[0])   int&

上の結果はすべて予想通りで、よく理解しています.C++11ではdecltypeは主にテンプレート関数を宣言するために用いられ,このテンプレート関数の戻り値タイプはそのパラメータタイプに依存する.たとえば、一例を見てみましょう.テンプレート関数を実装する必要があります.このテンプレート関数のパラメータには、四角カッコ([])インデックスをサポートするコンテナにintインデックス値を追加し、検証操作を行う必要があります.最後に、関数の戻りタイプはコンテナインデックスが操作する戻りタイプと同じである必要があります.
要素タイプTのコンテナで、operator[]の戻り値タイプはT&である必要があります.std::queueコンテナはこの要件を満たしています.std::vectorのほとんどが満たされています(std::vectorは例外ですが、operator[]はbool&ではなく、新しいオブジェクトを返します).したがって、ここでのコンテナオペレータoperator[]の戻り値タイプはキャパシタタイプに依存することに注意してください.
decltypeを使用すると、このテンプレート関数を簡単に実現できます.このテンプレートにはいくつかの改善が必要です.後で説明します.
template //        ,     。
auto authAndAccess(Container& c, Index i) 
-> decltype(c[i]) 
{
    authenticateUser();
    return c[i];
}

ここのautoは何の推測もしていないことに注意してください.ここでC++11が使用されていることを示すトレーシング戻りタイプ構文にすぎません.つまり、関数戻りタイプはパラメータリストの後に宣言されます(「->」の後)、関数パラメータを使用して関数戻りタイプを宣言できるという利点があります.(戻りタイプを関数の前に配置する場合、ここでのパラメータcおよびiはまだ宣言されていないため、使用できません).
C++14ではトレーラー戻りタイプは無視でき,上記の実装はautoのみになる.この形式の宣言を使用すると、タイプ推定を行うことを意味します.コンパイラは、関数の実装に基づいて関数の戻りタイプを推定します.
template // C++14,     
auto authAndAccess(Container& c, Index i) 
{
    authenticateUser();
    return c[i];//  c[i]      
}

上の投稿の最後の説明では、autoを関数として返すタイプを使用すると、コンパイラはテンプレートタイプを使用して返すタイプを推定します.この場合、上の関数に問題があります.ほとんどの要素タイプがTのコンテナではoperator[]はT&を返しますが、テンプレートタイプ推定では、初期化に使用する式の参照プロパティが無視されると説明されています.次のコードを見てください.
std::deque d;
…
authAndAccess(d, 5) = 10; //    d[5],  10,     

ここでd[5]はint&を返すが、authAndAccessのauto戻りタイプ推定では参照が削除され、最後の戻り値タイプは右の値intとなる.C++では10を右のintに割り当てることが禁止されているため、コンパイルに失敗しました.
我々が望む、すなわち、トレーラー戻りタイプを使用しないためには、戻りタイプに対してdecltypeタイプを使用して推定する必要がある.すなわち、関数authAndAccessと式c[i]を指定して同じタイプを返す必要がある.C++14ではdecltype(auto)フラグを使用して目的を達成します.autoはタイプ推定を行うことを示し、decltypeは推定中にdecltype推定ルールが使用されることを示します.最後の実装autoAndAccessは以下の通りである.
template // C++14,     ,      
decltype(auto) authAndAccess(Container& c, Index i) 
{
    authenticateUser();
    return c[i];//  c[i]      
}

これでauthAndAccessはc[i]が返す.c[i]がT&を返すとauthAndAccessもT&を返す.c[i]がオブジェクトを返すとauthAccessもオブジェクトを返します.
decltype(auto)の使用は、関数の戻りタイプに制限されず、変数の宣言にも使用できます.
Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // auto     ,myWidget1     Widget,   const       
decltype(auto) myWidget2 = cw;//myWidget2    const Widget& ,       decltype    

authAndAccessの最後のバージョンでは、この関数がまだ改善されていることについて言及しましたが、どうすればいいのでしょうか.関数宣言をもう一度見てください.
template
decltype(auto) authAndAccess(Container& c, Index i);

ここで、関数パラメータはconst以外の左値を指す参照で渡され、コンテナ内の要素の参照をクライアントに返すと、クライアントが変更できるようになります.左の参照である以上、この関数に右の値を渡すことはできません.しかし、右の値を関数に渡すことは意味があるかもしれません.クライアントは、コンテナ内の要素のコピーを取得したいだけです.次の例を参照してください.
std::deque<:string> makeStringDeque(); //     
//    makeStringDeque    deque         
auto s = authAndAccess(makeStringDeque(), 5);

そのため、この関数が左の値も右の値も受け入れられるように、関数を改訂する必要があります.リロードを使用可能(1つの関数は左参照パラメータを宣言し、1つの関数は右参照パラメータを宣言する)が、2つの関数を維持する必要があります.universal referenceパラメータタイプを使用して、このような状況を回避することができます.このパラメータタイプは右値にバインドすることも、左値にバインドすることもできます.最後にauthAndAccessは次のように宣言できます.
template 
decltype(auto) authAndAccess(Container&& c, Index i); 

このテンプレート関数では、操作が必要なコンテナのタイプや、コンテナ内の要素のタイプもわかりません.そのタイプを知らないオブジェクトに対して値別に渡すと、不要なコピーによるパフォーマンスの問題が発生したり、オブジェクトのスライスの問題が発生したりする可能性がありますが、ここではコンテナインデックスを使用して関数の戻り値を取得します.標準テンプレートライブラリのインスタンスをシミュレートして、合理的に見えるようにします(たとえば、std::string、std::vector、std::deque、)、したがって、値別転送を使用し続けます.
戻り値から右のプロパティを渡すには、univversal referenceにstd::forward:
template // C++14,    
decltype(auto) authAndAccess(Container&& c, Index i) 
{
    authenticateUser();
    return  std::forward(c)[i];
}

上の関数はC++14のコンパイラを使用する必要があります.ない場合は、C++11のテンプレートバージョンを使用することもできます.C++14とは異なり、自分で戻りタイプを指定する必要があります.
template // C++14,    
decltype(auto) authAndAccess(Container&& c, Index i) ->decltype(std::forward(c)[i])
{
    authenticateUser();
    return  std::forward(c)[i];
}

もう一つの問題を説明する必要があります.decltypeは多くの場合、あなたが望んでいるタイプを返しますが、いくつかの例外があります.decltypeをよりよく理解するために、私たちもこれらの状況を熟知する必要があります.
decltypeを変数名に適用すると、この変数名と同じタイプが生成されます.この場合例外はありません.しかし、左値式では複雑です.decltypeは、左値式に作用するときに生成されるタイプが左値参照であることを確認します.すなわち、変数名ではなく左値式のタイプがTである場合、decltype(左値式)のタイプはT&である.ほとんどの場合、左値式には左値参照識別子が表示されるため、影響はありません.たとえば、関数が左の値を返すと、通常は左の値参照を返します.&識別子も含まれます.
しかし、注意しなければならないことがあります.
int x =0;

xは変数名であるためdecltype(x)のタイプはintである.ただし、xをカッコ()で囲むと式が生成され、式(x)も左の値になるためdecltype((x))はint&となります.
c++14のdecltype(auto):
decltype(auto) f1()
{
    int x = 0;
    …
    return x; // decltype(x)  int,f1  int
}
decltype(auto) f2()
{
    int x = 0;
    …
    return (x); // decltype((x))   int&,  f2    int&
}

2つ目のケースは、戻り値が変化しただけでなく、ローカル変数への参照を返します.そのため、このような間違いの発生に警戒しなければならない.
最後にまとめます.
  • ほとんどの場合decltypeが変数名または式で生成されるタイプは変化しません.
  • decltypeが左値式に作用する場合、生成されるタイプはT&である.
  • C++14のdecltype(audo)を用いてタイプ推定を行う場合、decltype推定規則を用いて推定を行う.