コンストラクタからテンプレートパラメータを推測しないのはなぜですか?


102

今日の私の質問は非常に単純です:コンパイラーがクラスコンストラクターからテンプレートパラメーターを推測できないのはなぜですか?たとえば、次のコードが有効でなかった理由:

template<typename obj>
class Variable {
      obj data;
      public: Variable(obj d)
              {
                   data = d;
              }
};

int main()
{
    int num = 2;
    Variable var(num); //would be equivalent to Variable<int> var(num),
    return 0;          //but actually a compile error
}

私が言うように、これは有効ではないことを理解しているので、私の質問はなぜそうではないのですか?これを許可すると、主要な構文上の穴が作成されますか?この機能が不要なインスタンスはありますか(タイプを推測すると問題が発生します)?関数のテンプレート推論を許可する背後にあるロジックを理解しようとしていますが、適切に構築されたクラスではありません。


ドラハカールとピティスの答えを(少なくとも)コンパイルできない理由を誰かに(私はそうしますが、今は違います)招待します。それが機能しない理由の良い反例として
jpinto3912

2
また、これは簡単に介して、回避されることに注意してくださいtemplate<class T> Variable<T> make_Variable(T&& p) {return Variable<T>(std::forward<T>(p));}
ダックMooing

3
必要なものを得ることができますvar = Variable <decltype(n)>(n);
QuentinUK、2016

18
C ++ 17はこれを許可します!この提案は承認されました:open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html
underscore_d

1
@underscore_dすばらしい!良い頃!それは私が自然に感じた方法であり、それが機能する方法であり、それが機能しない刺激の原因です。
amdn '19 / 07/19

回答:


46

コンストラクタは常にクラスの唯一のエントリポイントではないため、これは有効ではないと思います(私はコピーコンストラクタとoperator =について話しています)。したがって、次のようにクラスを使用しているとします。

MyClass m(string s);
MyClass *pm;
*pm = m;

MyClass pmがどのテンプレートタイプであるかをパーサーが知ることがそれほど明白かどうかはわかりません。

私が言ったことが意味を成しているかどうかはわかりませんが、コメントを自由に追加してください。それは興味深い質問です。

C ++ 17

C ++ 17がコンストラクターの引数から型の推論を受けることは認められています。

例:

std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);

受理された論文


8
これは実は私が考えたことのない素晴らしい点です。ポインターが型固有でなければならない(つまり、MyClass <string> * pmでなければならない)という事実を回避する方法はありません。その場合は、インスタンス化時に型を指定する手間を省くだけで済みます。余分な作業のいくつかの単なる文字(オブジェクトがスタック上に作成され、上記のようにヒープではない場合のみ)。私はいつもクラスの推論がワームの構文缶を開けるかもしれないといつも思っていました。
GRB、

2
2行目のように、コンストラクターからのテンプレートパラメーターの推論を許可するために、コンストラクター呼び出しなしで特殊化されていない宣言許可することがどのように必要になるのか、私にはよくわかりません。MyClass *pmつまり、宣言された関数template <typename T> void foo();を明示的な特殊化なしに呼び出すことができないのと同じ理由で、ここは無効になります。
カイルストランド

3
@KyleStrandはい、「クラステンプレートの引数はコンストラクターから推定できないため、[コンストラクターを使用しない例]」と言った場合、この答えはまったく関係ありません。私はそれが受け入れられ、+ 29に達し、明白な問題に気づくのに6年かかり、7年間は1つの反対票もなく座っていたとは本当に信じられません。彼らが読んでいる間、他の誰も考えませんか、それとも...?
underscore_d

1
@underscore_d私は現在のところ、この回答には「この提案には問題がある可能性があります。今言ったことが理にかなっている(!)か、気軽にコメントできる(!!)ちなみに、これはC ++ 17が動作する方法とまったく同じです。」
カイルストランド

1
@KyleStrandああ、それはまた別の問題です。私はそれに気づきましたが、他のすべての楽しみの中で言及するのを忘れていました。C ++ 17に関する編集はOPによるものではありませんでした...そしてIMOは承認されるべきではありませんでしたが、新しい回答として投稿されました:投稿があったとしても、「投稿の意味の変更」として格下げされます。始めても意味がありません...私は完全に新しいセクションの編集が公正なゲームであることを認識していなかったし、劇的な編集が拒否されたことは確かにありませんでした。
underscore_d

27

他の人が対処した理由のためにあなたが求めることを行うことはできませんが、これを行うことができます:

template<typename T>
class Variable {
    public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
  return Variable<T>(instance);
}

すべての意図と目的のためにあなたが求めるものと同じです。カプセル化が好きな場合は、make_variableを静的メンバー関数にすることができます。これが、名前付きコンストラクタと呼ばれるものです。したがって、それはあなたが望むことをするだけでなく、それはほとんどあなたが望むものと呼ばれます:コンパイラは(名前付き)コンストラクタからテンプレートパラメータを推論します。

注意:あなたが次のようなものを書くとき、合理的なコンパイラーは一時オブジェクトを最適化します

auto v = make_variable(instance);

6
このような場合、関数の静的メンバーを作成することは特に有用ではないことを指摘してください。そのため、クラスのテンプレート引数を指定してそれを呼び出す必要があるため、それを推定しても意味がありません。
Predelnik、2014

3
C ++ 11ではさらに優れauto v = make_variable(instance)ているため、実際に型を指定する必要はありません。
Claudiu

1
ええ、make関数をstaticメンバーとして宣言するという考えでlol ...ちょっと考えてみてください。それはさておき:無料メイク機能は確かだった解決策が、それはあなたがしているタイピングをしながら、あなただけのことを、冗長定型のたくさんだ知っているコンパイラはあなたが繰り返している全ての情報へのアクセスを持っているので、あなたがする必要はありません。 ..そしてありがたいことにC ++ 17はそれを正規化します。
underscore_d

21

2016年の賢明な時代に、この質問が出されてから2つの新しい標準が登場し、すぐに新しい標準が登場したため、C ++ 17標準をサポートするコンパイラがコードをそのままコンパイルすることを知っておくことが重要です。

C ++ 17でのクラステンプレートのテンプレート引数の控除

ここに(承認された回答のOlzhas Zhumabekによる編集の礼儀)は、規格への関連する変更を詳述した論文です。

他の回答からの懸念への対処

現在の最高評価の答え

この回答は、「コピーコンストラクターとoperator=」は正しいテンプレートの特殊化を知らないことを指摘しています。

標準のコピーコンストラクターoperator= が存在し、既知のテンプレートタイプに対してのみ存在するため、これはナンセンスです。

template <typename T>
class MyClass {
    MyClass(const MyClass&) =default;
    ... etc...
};

// usage example modified from the answer
MyClass m(string("blah blah blah"));
MyClass *pm;   // WHAT IS THIS?
*pm = m;

私はコメントで述べたようにここでは、存在しない理由のためのMyClass *pm推論の新しいフォームの有無にかかわらず、法的宣言すべき:MyClass タイプではありません(それはテンプレートだ)、それはのポインタを宣言しても意味がありませんので、タイプMyClass。この例を修正する方法の1つを次に示します。

MyClass m(string("blah blah blah"));
decltype(m) *pm;               // uses type inference!
*pm = m;

ここでpmは、すでに正しいタイプであるため、推論は簡単です。さらに、コピーコンストラクターを呼び出すときに誤って型を混在させることはできません。

MyClass m(string("blah blah blah"));
auto pm = &(MyClass(m));

ここでpmは、のコピーへのポインタになりますm。ここでMyClassは、からコピーが構築されています。mこれはタイプですMyClass<string>(存在しないタイプではありませんMyClass)。したがって、pmのタイプが推定される時点で、のテンプレートタイプ、つまりのテンプレートタイプがmであることを知るのに十分な情報pmがありstringます。

さらに、次の場合は常に コンパイルエラーが発生します

MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;

これは、コピーコンストラクターの宣言がテンプレート化されていないためです。

MyClass(const MyClass&);

ここで、copy-constructor引数のtemplate-typeは、クラス全体のtemplate-typeと一致しています。すなわち、時にMyClass<string>インスタンス化され、MyClass<string>::MyClass(const MyClass<string>&);それをインスタンス化され、そしてときにMyClass<int>インスタンス化され、MyClass<int>::MyClass(const MyClass<int>&);インスタンス化されます。明示的に指定されていないか、テンプレート化されたコンストラクターが宣言されていない限り、コンパイラーがをインスタンス化する理由はありませんMyClass<int>::MyClass(const MyClass<string>&);。これは明らかに不適切です。

CătălinPitișによる回答

Pitişは推測する例を示しますVariable<int>Variable<double>、次に述べ、:

2つの異なる型(変数と変数)のコードに同じ型名(変数)があります。私の主観的な観点から、それはコードの可読性にかなり影響します。

前の例で述べたように、新機能により構文的には1つのように見えますVariableが、それ自体は型名ではありません

次に、ピチッチは、適切な推論を可能にするコンストラクターが提供されない場合にどうなるかを尋ねます。答えは、推論はコンストラクター呼び出しによってトリガーされるため、推論は許可されないということです。コンストラクタ呼び出しがなければ、推論はありません

これは、fooここで推定されたのバージョンを尋ねるのと同じです。

template <typename T> foo();
foo();

答えは、述べられた理由により、このコードは違法であるということです。

MSalterの回答

これは、私が知る限り、提案された機能に関する正当な懸念を提起する唯一の答えです。

例は次のとおりです。

Variable var(num);  // If equivalent to Variable<int> var(num),
Variable var2(var); // Variable<int> or Variable<Variable<int>> ?

重要な問題は、コンパイラーがここで型推論れたコンストラクターを選択するか、それともコピーコンストラクターを選択するかです。

コードを試してみると、コピーコンストラクターが選択されていることがわかります。例を拡張するには

Variable var(num);          // infering ctor
Variable var2(var);         // copy ctor
Variable var3(move(var));   // move ctor
// Variable var4(Variable(num));     // compiler error

提案と標準の新しいバージョンがこれをどのように指定しているかはわかりません。それは私がまだ理解していない新しい標準のビットである「控除ガイド」によって決定されるようです。

var4控除が違法である理由もわかりません。g ++からのコンパイラエラーは、ステートメントが関数宣言として解析されていることを示しているようです。


なんてすばらしい、詳細な答えでしょう! var4「最も厄介な解析」の単なる例です(テンプレート引数の控除とは関係ありません)。これまでは余分な括弧を使用していましたが、最近では中括弧を使用して構造を明確に示すことが通常のアドバイスだと思います。
Sumudu Fernando、2018

@SumuduFernandoありがとう!それVariable var4(Variable(num));が関数宣言として扱われるということですか?もしそうなら、なぜVariable(num)有効なパラメーター指定なのですか?
カイルストランド

@SumuduFernando気にしないでください、これが有効だとは思いもしませんでした:coliru.stacked-crooked.com/a/98c36b8082660941
カイルストランド

11

まだ足りない:次のコードがあいまいになります。

int main()
{
    int num = 2;
    Variable var(num);  // If equivalent to Variable<int> var(num),
    Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}

別の良い点。Variable(Variable <obj> d)で定義されたコピーコンストラクターが存在すると仮定すると、何らかの優先順位を確立する必要があります。
GRB、

1
または、代わりに、Pitisの回答に関して私が提案したように、コンパイラーに未定義のテンプレートパラメーターエラーを再度スローさせます。ただし、この方法をとると、問題(エラー)なしに推論が発生する可能性のある回数がますます少なくなります。
GRB、

これは実際には興味深い点であり、(私の回答で述べたように)承認されたC ++ 17の提案がこれをどのように解決するかはまだわかりません。
カイルストランド

9

コンパイラがあなたの要求をサポートするとします。その後、このコードは有効です:

Variable v1( 10); // Variable<int>

// Some code here

Variable v2( 20.4); // Variable<double>

これで、2つの異なる型(変数と変数)のコードに同じ型名(変数)があります。私の主観的な観点から、それはコードの可読性にかなり影響します。同じ名前空間の2つの異なる型に同じ型名を付けると、誤解を招くように見えます。

後の更新: 考慮すべきもう1つのこと:テンプレートの部分的(または完全)特殊化。

変数を特化して、期待するようなコンストラクターを提供しない場合はどうなりますか?

だから私は持っているでしょう:

template<>
class Variable<int>
{
// Provide default constructor only.
};

それから私はコードを持っています:

Variable v( 10);

コンパイラは何をすべきですか?汎用のVariableクラス定義を使用してVariableであると推定し、Variableが1つのパラメーターコンストラクターを提供していないことを発見しますか?


1
さらに悪いことに、Variable <int> :: Variable(float)しかない場合はどうなりますか?現在、Variable(1f)を推定する方法は2つあり、Variable(1)を推定する方法はありません。
MSalters 2009年

それは良い点ですが、キャストすることで簡単に超えることができます:変数v1((double)10)
jpinto3912

コードの可読性は主観的な問題であることに同意しますが、テンプレートの特殊化に関するあなたの発言には100%同意します。解決策はおそらく、未定義のテンプレートパラメータエラーを生成することです(コンパイラが<int>特殊化を見て、有効なコンストラクタがないことを確認したら、どのテンプレートを使用するかわからず、明示的に指定する必要があると言っている場合)。私はそれがかなりの解決策ではないことに同意します。これは、対処する必要があるもう1つの主要な構文上の穴として追加します(ただし、結果を受け入れると解決できます)。
GRB、

4
@ jpinto3912-あなたは要点を逃しています。コンパイラーは、すべての可能なVariable <T>をインスタンス化して、いずれかのctor Variable <T> :: Variableがあいまいなctorを提供しているかどうかを確認する必要があります。あいまいさを取り除くことは問題ではありません-それが必要な場合は、Variable <double>を自分でインスタンス化するだけです。そもそものあいまいさが原因となって、それが不可能になっています。
MSalters 2009年

6

C ++ 03およびC ++ 11標準では、構成子に渡されるパラメーターからのテンプレート引数の推定は許可されていません。

しかし、「コンストラクタのテンプレートパラメータの控除」の提案があるので、すぐに求めているものを手に入れることができます。編集:実際、この機能はC ++ 17で確認されています。

参照:http : //www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.htmlおよびhttp://www.open-std.org/jtc1/sc22/wg21/docs/ papers / 2015 / p0091r0.html


この機能はC ++ 17に追加されましたが、「すぐに」が6〜8年の期間に適用される場合は追加されません。;)
ChetS

2

多くのクラスはコンストラクターパラメーターに依存しません。コンストラクターが1つだけあり、このコンストラクターのタイプに基づいてパラメーター化するクラスはわずかです。

テンプレートの推論が本当に必要な場合は、ヘルパー関数を使用します。

template<typename obj>
class Variable 
{
      obj data;
public: 
      Variable(obj d)
      : data(d)
      { }
};

template<typename obj>
inline Variable<obj> makeVariable(const obj& d)
{
    return Variable<obj>(d);
}

1
もちろん、この機能は一部のクラスでのみ役立ちますが、関数の推論についても同じことが言えます。すべてのテンプレート関数が引数リストからパラメータを取得するわけではありませんが、そうした関数の推論は可能です。
GRB、

1

型の推論は現在のC ++のテンプレート関数に限定されていますが、他のコンテキストでの型の推論は非常に役立つことが長い間認識されてきました。したがって、C ++ 0x autoです。

一方で、正確に何を示唆すると、C ++ 0xのではできません、次のショーでは、あなたはかなり近づくことができます。

template <class X>
Variable<typename std::remove_reference<X>::type> MakeVariable(X&& x)
{
    // remove reference required for the case that x is an lvalue
    return Variable<typename std::remove_reference<X>::type>(std::forward(x));
}

void test()
{
    auto v = MakeVariable(2); // v is of type Variable<int>
}

0

あなたは正しいですが、コンパイラは簡単に推測できますが、私が知る限り、標準またはC ++ 0xにはないため、コンパイラプロバイダがこの機能を追加する前に、少なくとも10年(ISO標準の固定回転率)待つ必要があります。


次の標準ではこれは正しくありません。autoキーワードが導入されます。このスレッドのJames Hopkinsの投稿をご覧ください。stackoverflow.com/questions/984394/…。彼はそれがC ++ 0xでどのように可能になるかを示しています。
ovane 2009年

1
自分を正すために、autoキーワードも現在の標準にありますが、目的は異なります。
ovanes 2009年

(この回答の時から)8年になるように見えます...そのため、この間に2つの基準があったとしても、10年は悪い推測ではありませんでした。
カイルストランド

-1

std :: vectorを使い慣れているクラスを参照して、問題を見てみましょう。

まず、非常に一般的なベクターの使用法は、パラメーターを取らないコンストラクターを使用することです。

vector <int> v;

この場合、明らかに推論は実行できません。

2番目の一般的な使用法は、事前にサイズ設定されたベクトルを作成することです。

vector <string> v(100);

ここで、推論が使用された場合:

vector v(100);

文字列ではなく整数のベクトルを取得しますが、おそらくサイズが設定されていません!

最後に、「推論」を使用して、複数のパラメーターを取るコンストラクターを検討してください。

vector v( 100, foobar() );      // foobar is some class

推論に使用するパラメーターはどれですか?コンパイラーに2番目のコンパイラーであることを伝える何らかの方法が必要です。

ベクトルのように単純なクラスのこれらすべての問題により、推論が使用されない理由が簡単にわかります。


3
あなたはその考えを誤解していると思います。コンストラクターの型推論は、テンプレート型がコンストラクターの一部である場合にのみ発生します。vectorにテンプレート定義template <typename T>があるとします。ベクトルのコンストラクタはvector(T size)ではなくvector(int size)として定義されるため、この例は問題ではありません。vector(Tサイズ)の場合にのみ、推論が発生します。最初の例では、コンパイラーはTが未定義であるというエラーを出します。関数テンプレートの推論が機能する方法と本質的に同じです。
GRB、

それで、それは、単一のパラメーターを持ち、そのパラメーターがテンプレートパラメータータイプであるコンストラクターに対してのみ発生しますか?それは、インスタンスの数が非常に少ないようです。

必ずしも単一のパラメータである必要はありません。たとえば、vector(int size、T firstElement)のベクターコンストラクターを持つことができます。テンプレートに複数のパラメーター(template <typename T、typename U>)がある場合、Holder :: Holder(T firstObject、U secondObject)を持つことができます。テンプレートに複数のパラメーターがあるが、コンストラクターがそれらの1つのみを受け取る場合(例:Holder(U secondObject))、Tは常に明示的に指定する必要があります。ルールは、関数テンプレートの推論にできるだけ類似するように意図されています。
GRB、

-2

Actorをテンプレートにすることで、Variableは1つの形式のみを持つことができますが、さまざまなctorを持つことができます。

class Variable {
      obj data; // let the compiler guess
      public:
      template<typename obj>
      Variable(obj d)
       {
           data = d;
       }
};

int main()
{
    int num = 2;
    Variable var(num);  // Variable::data int?

    float num2 = 2.0f;
    Variable var2(num2);  // Variable::data float?
    return 0;         
}

見る?Variable :: dataメンバーを複数持つことはできません。


これはどのシナリオでも意味がありません。そのクラスはもはやテンプレートではないので、objデータに関するobjは未定義です。このようなコードはどちらの方法でも無効になります。
GRB、

私はあなたが説明するコンパイラの動作を望んでいたので、その制限を回避する方法を見つけました(私の場合)。興味深いことに、stackoverflow.com / questions / 228620 / garbage-collection-
ニックダンドゥラキス09年

-2

詳細については、C ++テンプレート引数の控除を参照してください。


4
私は以前この記事を読みましたが、私が言っていることについてはあまり話されていなかったようです。筆者がクラスに関して引数の控除について話すように思われるのは、記事の冒頭でそれを行うことができないと彼が言ったときだけです;)-あなたが関連すると思うセクションを指摘できれば本当に感謝しています。
GRB、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.