web-dev-qa-db-ja.com

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

今日の私の質問は非常に簡単です:コンパイラーは、クラスコンストラクターからテンプレートパラメーターを推論できないのは、関数パラメーターからできるのと同じですか?たとえば、次のコードが有効でなかった理由は次のとおりです。

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
}

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

102
GRB

コンストラクターは常にクラスの唯一のエントリポイントではないため、有効ではないと思います(コピーコンストラクターと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);

受理された論文

46
Drahakar

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

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

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

NB:妥当なコンパイラは、次のようなものを書くときに一時オブジェクトを最適化します

Variable<T> v = make_variable(instance);
27
Lionel

2016年の啓発された時代に、この質問が出されてから2つの新しい標準があり、新しいものが間もなく登場するので、知っておくべき重要なことは、C++をサポートするコンパイラーです17標準は コードをそのままコンパイルします

C++ 17のクラステンプレートのテンプレート引数の推論

ここ (受け入れられた回答のOlzhas Zhumabekによる編集の礼儀)は、標準への関連する変更を詳述する論文です。

他の回答からの懸念に対処する

現在最も評価の高い回答

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

標準のコピーコンストラクタと_operator=_ only existknownテンプレートタイプであるため、これはナンセンスです。

_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 type(テンプレート)ではないため、MyClass型のポインターを宣言しても意味がありません。この例を修正する1つの可能な方法を次に示します。

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

ここで、pmは正しいタイプのalreadyであるため、推論は簡単です。さらに、copy-constructorを呼び出すときに、誤ってmixタイプにすることはできません。

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

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

さらに、以下はalwaysコンパイルエラーを発生させる です。

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

これは、コピーコンストラクターの宣言がnot templatedであるためです。

_MyClass(const MyClass&);
_

ここでは、copy-constructor引数のテンプレートタイプmatchesクラス全体のテンプレートタイプ。つまり、_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つの異なるタイプ(変数と変数)のコードに同じタイプ名(変数)があります。私の主観的な観点からは、コードの可読性にほとんど影響します。

前の例で述べたように、Variable自体はnot型名ですが、新しい機能により構文的には1つのように見えます。

その後、Pitișは、適切な推論を許可するコンストラクターが指定されていない場合はどうなるかを尋ねます。答えは、推論はconstructor callによってトリガーされるため、許可されないということです。 constructor-callがない場合、推論なしがあります。

これは、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>> ?
_

重要な質問は、コンパイラがここでtype-in​​ferredコンストラクターまたはcopyコンストラクターを選択するかどうかです。

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

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

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

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

21
Kyle Strand

まだありません:次のコードは非常に曖昧になります:

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

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

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

// Some code here

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

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

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

Variableを専門にし、期待どおりのコンストラクタを提供しない場合はどうなりますか?

だから私は持っているだろう:

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

その後、私はコードを持っています:

Variable v( 10);

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

9

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

6
ChetS

多くのクラスは、コンストラクターのパラメーターに依存しません。コンストラクターが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);
}
2
rlbond

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

exactly 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>
}
1
James Hopkin

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

0
Robert Gould