web-dev-qa-db-ja.com

C ++テンプレート引数型の控除

次のように宣言されたテンプレート関数があるとします。

template<class T>
int Function(T object);

ユーザーは、次のようにテンプレート化されたタイプを指定することにより、この関数を呼び出すことができます。

int result = Function<float>(100.f); // Valid

ただし、コンパイラは指定された引数の型からTの型を推定できるため、型の指定はオプションです。このような:

int result = Function(100.f); // Also valid, the compiler deduced the type "float" from the literal's type

もう少し複雑になり、次のようなテンプレート値パラメーターが必要だとします。

template<class T, T* object>
int Function();

この方法で関数を呼び出すことができます:

static float val = 100.f;
// ...
int result = Function<float, &val>();

私の質問は:引数の型&valに基づいてT型を推測するようコンパイラーに強制する方法はありますか?

次のコードを有効にする方法が必要です。

static float val = 100.f;
// ...
int result = Function<&val>();

できますか?

14

C++ 17では、auto非型テンプレートパラメータを使用できます。これで問題を解決できます。

何かlike

_template<auto object, class T=std::decay_t<decltype(*object)>>
int Function();
_

Tの本体内にタイプFunctionが必要であると想定)

C++ 14では、C++ 17機能がありません。欠落していたため、正確に追加されました。回避策には、#define UGLY_HACK(...) decltype(__VA_ARGS__), __VA_ARGS__などのマクロが含まれます。

注:ここでの答えは、効果的な最新のC++から借用したもので、自分の(非常に)いくつか追加しただけです

これは、提起するのは簡単ですが、答えるのが難しい質問の1つです。私はch全体を読んだことを覚えています。テンプレートタイプの控除について、そして新人の読者にとっては、1つの読みでも答えは明確ではありません。それにもかかわらず、私はそれをここで明確にしようとします。

テンプレートタイプの推定に影響するniversal References(参照またはr値参照とは異なる)と呼ばれるものがあることに注意してください。読者は知っていると思いますl値およびr値の参照。

ユビキタス関数テンプレートの定義は次のようになります。

template <typename T>
returnType function(paramType param);

Functionの呼び出しは、次のようになります。

function(expression);

コンパイラーはを使用して[〜#〜] t [〜#〜]のタイプとparamTypeのタイプを判別します。これは、より頻繁にparamTypeconstconst&const &&などの装飾が含まれるためです。初心者は魅力的ですコンパイラによって推定される型[〜#〜] t [〜#〜]の型と同じになる、つまり、引数が関数ですが、常にそうであるとは限りません。タイプの推定[〜#〜] t [〜#〜]は、expressionparamTypeの両方に依存します。関数パラメーターparamTypeに応じて、テンプレートタイプの推定で考慮すべき3つのケースがあります。

  1. paramTypeはポインタまたは参照ですが、ユニバーサル参照ではありません
  2. paramTypeは普遍的な参照です
  3. paramTypeはポインタでも参照でもありません。

それぞれのケースを一つずつ見てみましょう

ケース1:paramTypeはポインターまたは参照ですが、ユニバーサル参照ではありません

私をクレイジーと呼んでください、しかしこれは遭遇することができる最も単純なケースです。この場合、型の推論は次のように機能します。(i)expressionが参照の場合、参照部分を無視します(ii)次に一致しますパターンparamTypeに対して決定[〜#〜] t [〜#〜]

例を見てみましょう:

template <typename T>
returnType function(T &param);

次の変数宣言があります。

int x = 23;               // x is int
const int const_x = x;    // const_x is const int
const int& ref_x = x;     // ref_x is a reference to x as const int

さまざまな呼び出しにおける[〜#〜] t [〜#〜]およびparamの推定呼び出しは次のとおりです。続く:

f(x);                    //T is int, param's type is int&
f(const_x);              //T is const int, param's type is const int&
f(ref_x);                //T is const int, param's type is const int&

ここで注意すべき点が2つあります。

(i)コンパイラーはここで型の推論の参照性を無視します

(ii)const-nessは、constオブジェクトまたはconstオブジェクトへの参照を渡すと、タイプ[〜#〜] t [〜#〜]の一部になります。したがって、constオブジェクトまたはconstオブジェクトへの参照をパラメーターを取る関数に渡すT&は安全です。

関数パラメーターをT&からconst T&に変更すると、この場合はparamconstを参照するため、const-nessは一部として推定する必要はありません[〜#〜] t [〜#〜]の以下に例を示します。

template <typename T>
returnType function(const T& param);  // param is now a ref-to-const

int x = 23;                    // same as previous
const int const_x = x;         // same as previous
const int& ref_x = x;          // same as previous

f(x);                         // T is int, paramType is const int&
f(const_x);                   // T is int, paramType is const int&
f(ref_x);                     // T is int, paramType is const int&

:変数 'x'は 'f()'へのconst引数ではありませんが、const paramとして推定されます

paramTypeがポインタの場合、物事は基本的に参照の場合と同じように機能します。参照の代わりにポインタがあります。たとえば、完全を期すために以下が提供されています。

template <typename T>
returnType function( T* paramType);  // paramType is now a pointer

int x = 23;                      // same as before
const int *pointer_x = &x;       // pointer_x is pointer to x as const int

f(&x);                          // T is int, paramType is int*
f(pointer_x);                   // T is const int, paramType is const int*

完全を期すために、paramTypeが次のような定数オブジェクトへのポインターである場合は、ケースを投稿することもできます。

template <typename T>
returnType function(const T* paramType);

int x = 23;                      // same as before
const int *pointer_x = &x;       // pointer_x is pointer to x as const int

f(&x);                          // T is int, paramType is const int*
f(pointer_x);                  // T is int, paramType is const int*

つまり、再びconst-nessはTの一部として推定されなくなります

R値参照の場合は、次のように入力します[〜#〜] t [〜#〜]およびparamType控除は、l値参照の場合と基本的に同じ規則に従います。

これは、最初のケースのほとんどをカバーしています。ケース2を見てみましょう。

ケース2:paramTypeは普遍的な参照です

ユニバーサル参照は、r値参照のように宣言されますが、l値を取りますが、それらの動作が異なるのは、関数の引数がl値参照を受け取ることです。この場合の型の推論のしくみは次のとおりです。

(i)がl値の場合、両方の[〜#〜] t [〜#〜] =およびparamTypeは、l値であると推定されます。 (これは、コードがどのように見えるかを考えると奇妙に思えますparamTypeはr値参照の構文を使用して宣言されていますが、その推定型はl値参照であるためです。)これは[〜#〜] t [〜#〜]が参照であると推定される唯一のケースであることに注意してください。

以下の例は私の説明を明確にします:

template <typename T>
returnType function(T&& paramType);  // param becomes universal reference if
                                     // argument to function call is an l-value

int x = 23                     // same as previous
const int const_x = x;         // same as previous
const int& ref_x = x;          // same as previous

f(x);             // x is l-value therefore T is int&
                  // paramType is int&

f(const_x);       // const_x is l-value therefore T is const int&
                  //paramType is also const int&

f(ref_x);        // ref_x is l-value therefore T is const int&
                 // paramType is also const int&

f(23);          // 27 is r-value so T is int
                // paramType is now int&&

私はここで正直に言いたいのですが、これは普遍的な参照が彼らのように機能する理由を説明していませんが、私がここでそれを正当化すると、この投稿は長くなりすぎると思います。

ケース3:paramTypeがポインターでも参照でもない##

これは、テンプレートで値渡しが行われる場所です。つまり、paramは、呼び出し元の関数の引数に渡されるすべてのコピー、つまり完全に新しいオブジェクトであることを意味し、これにより、型の推論を制御するルールが動機付けられます- [〜#〜] t [〜#〜] from expressionから。ここで注意すべき点は次の2つです。

(i)expressionrefrence-nessが偶然ある場合は無視します。

(ii)ref-nessを無視した後、const-nessまたはvolatile-nessも無視します。つまり、存在する場合

template <typename T>
returnType function(T paramType);

int x = 23;
const int const_x = x;
const int& ref_x = x;

f(x);             // T and paramType are both int
f(const_x);       // T and paramType are both int here too
f(ref_x);         // T and paramType are both int again

Const_xとref_xは変更できないconstオブジェクトですが、それらのコピーを変更できないわけではないことに注意してください。これは簡単に見えますが、定数ポインターを定数オブジェクトに渡すと、よりトリッキーになります。別の例を見てみましょう:

template <typename T>
returnType function(T param);

const double *const dPtr = 23;  // dPtr is const pointer to const double

function(dPtr);             // passing argument of type const double *const

constポインタが値で渡されると、const-nessが失われ、ポインタが値でコピーされます。これは、値渡しの型推論規則と同期しています、ただし、ポインタが指すconst-nessは保持されるため、paramTypeはconst * doubleになります。

これは私がそれについて学び始めたときに私がしたようにあなたの頭を回転させるかもしれません。最善の方法は、それを再読してコード化することです。

10
ggulgulia