112

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

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
}

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

4

12 に答える 12

49

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

採択論文.

于 2009-06-12T01:18:02.247 に答える
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);
于 2011-07-28T15:36:24.187 に答える
23

啓蒙された 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;

ここで、コメントで指摘したように、新しい形式の推論の有無にかかわらず、正当な宣言になる理由ありません: は型ではありません(テンプレートです)。したがって、のポインターを宣言するのは意味がありません。タイプ。この例を修正する方法の 1 つを次に示します。MyClass *pmMyClass MyClass

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の型が推測される時点で、 のテンプレート型、したがって のテンプレート型がであることを知るのに十分な情報があります。mpmstring

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

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 つの異なる型 (Variable と Variable) のコードに同じ型名 (Variable) があります。私の主観的な観点からは、コードの可読性にかなり影響します。

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

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

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++ からのコンパイラ エラーは、ステートメントが関数宣言として解析されていることを示しているようです。

于 2016-08-22T19:28:14.590 に答える
12

まだ欠落しています: 次のコードは非常にあいまいになります:

int main()
{
    int num = 2;
    Variable var(num);  // If equivalent to Variable<int> var(num),
    Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}
于 2009-06-12T15:01:52.017 に答える
9

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

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

// Some code here

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

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

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

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

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

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

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

Variable v( 10);

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

于 2009-06-12T07:50:40.633 に答える
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/論文/2015/p0091r0.html

于 2013-04-11T00:22:44.327 に答える
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);
}
于 2009-06-12T01:46:50.447 に答える
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>
}
于 2009-06-12T11:36:09.157 に答える
-1

誰もがよく知っているはずのクラス std::vector を参照して問題を見てみましょう。

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

vector <int> v;

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

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

vector <string> v(100);

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

vector v(100);

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

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

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

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

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

于 2009-06-12T08:11:05.280 に答える
-2

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

于 2009-06-12T00:14:22.820 に答える