C ++がFoo <T> :: Foo(T &&)の呼び出しでTを推定できないのはなぜですか?


9

次のテンプレート構造があるとします。

template<typename T>
struct Foo {
    Foo(T&&) {}
};

これはコンパイルTされ、次のように推定されますint

auto f = Foo(2);

しかし、これはコンパイルされません:https : //godbolt.org/z/hAA9TE

int x = 2;
auto f = Foo(x);

/*
<source>:12:15: error: no viable constructor or deduction guide for deduction of template arguments of 'Foo'
    auto f = Foo(x);
             ^

<source>:7:5: note: candidate function [with T = int] not viable: no known conversion from 'int' to 'int &&' for 1st argument
    Foo(T&&) {}
    ^
*/

ただし、Foo<int&>(x)受け入れられます。

しかし、一見冗長なユーザー定義の控除ガイドを追加すると、機能します。

template<typename T>
Foo(T&&) -> Foo<T>;

ユーザー定義の控除ガイドがないTと、なぜint&控除できないのですか?



その質問は、次のようなテンプレートタイプに関するもののようですFoo<T<A>>
jtbandes

回答:


5

転送参照に関する合成控除ガイドには特定の例外があるため、ここで混乱が生じていると思います。

コンストラクターから生成されたクラステンプレート引数の控除を目的とした候補関数とユーザー定義の控除ガイドから生成された関数の外観はまったく同じです。

template<typename T>
auto f(T&&) -> Foo<T>;

ただし、コンストラクタから生成されたものの場合T&&は、単純な右辺値参照ですが、ユーザー定義の場合は転送参照です。これは、C ++ 17標準の[temp.deduct.call] / 3で指定されています(ドラフトN4659、私を強調):

転送基準 CV-修飾されていないテンプレートパラメータに右辺値参照であるクラステンプレートのテンプレートパラメータを表していない(クラステンプレート引数控除中([over.match.class.deduct]))。

したがって、クラスコンストラクターから合成された候補Tは、転送参照(T左辺値参照と推測できるため、左辺値参照でもある可能性があるT&&)から推測するのではなくT、非参照としてのみ推測するため、T&&常に右辺値参照。


1
明確で簡潔な回答をありがとうございます。参照の転送ルールでクラステンプレートパラメーターに例外がある理由を知っていますか?
jtbandes

2
@jtbandesこれは、米国の国家機関によるコメントの結果として変更されたようです。論文p0512r0を参照してください。コメントは見つかりませんでした。根拠のための私の推測では、あなたが右辺値参照を取るコンストラクタを記述する場合、あなたは通常、それはあなたが指定したかどうかを同じように動作することを期待していることであるFoo<int>(...)ばかりか、Foo(...)推測することができ参照(転送とそうではありませんこれは、Foo<int&>代わりに。
くるみ

6

ここでの問題は、クラスがでテンプレート化さTれているため、コンストラクターFoo(T&&)で型の推論を実行していないことです。常にr値参照があります。つまり、Foo実際のコンストラクタは次のようになります。

Foo(int&&)

Foo(2)2はprvalueであるため機能します。

Foo(x)xは、にバインドできない左辺値であるためですint&&。あなたはstd::move(x)それを適切なタイプにキャストするために行うことができます(デモ

Foo<int&>(x)コンストラクターがFoo(int&)参照の折りたたみルールによるものになるため、問題なく動作します。最初は標準に従ってFoo((int&)&&)崩壊しFoo(int&)ます。

「冗長な」控除ガイドに関して:最初は、基本的に次のようなヘルパー関数のように機能するコード用のデフォルトのテンプレート控除ガイドがあります。

template<typename T>
struct Foo {
    Foo(T&&) {}
};

template<typename T>
Foo<T> MakeFoo(std::add_rvalue_reference_t<T> value)
{
   return Foo<T>(std::move(value));
}

//... 
auto f = MakeFoo(x);

これは、標準により、この(架空の)テンプレートメソッドにはクラスと同じテンプレートパラメーター(Just T)があり、その後に任意のテンプレートパラメーターがコンストラクターとして続く(この場合はなし、コンストラクターはテンプレート化されていない)と規定されているためです。そして、関数パラメーターの型はコンストラクターの型と同じです。私たちの場合、をインスタンス化するFoo<int>と、コンストラクターはFoo(int&&)、つまり右辺値参照のようになります。したがって、add_rvalue_reference_t上記の使用法。

明らかにこれは機能しません。

「冗長な」控除ガイドを追加した場合:

template<typename T>
Foo(T&&) -> Foo<T>;

あなたは、コンパイラが接続参照のいずれかの種類にもかかわらず、ことを区別するために許可されT、コンストラクタ(中int&const int&またはint&&など)、あなたは参照せずにするクラスの推論タイプ(ジャストを意図しましたT)。私たちは突然、これはあるされている型推論を実行します。

次に、次のような(架空の)ヘルパー関数を生成します。

template<class U>
Foo<U> MakeFoo(U&& u)
{
   return Foo<U>(std::forward<U>(u));
}

// ...
auto f = MakeFoo(x);

(コンストラクターへの呼び出しは、クラステンプレート引数の推定のためにヘルパー関数にリダイレクトされるため、にFoo(x)なりますMakeFoo(x))。

これが可能にU&&なるためにint&T単純になるためにint


テンプレートの2番目のレベルは必要ないようです。それはどのような価値を提供しますか?ここでT &&が常に右辺値参照として扱われる理由を明確にするドキュメントへのリンクを提供できますか?
jtbandes

1
しかし、Tがまだ推定されていない場合、「Tに変換可能な任意の型」は何を意味するのでしょうか。
jtbandes

1
あなたはすぐに回避策を提供しますが、何らかの方法で変更しないと機能しない理由を説明に集中できますか?なぜそのままでは動かないのですか?" xはバインドできない左辺値です" ですが、int&&理解できない人は困惑するでしょFoo<int&>(x)う。
Wyck

2
@Wyck:理由にさらに焦点を当てるように投稿を更新しました。
AndyG

3
@Wyck本当の理由は非常に単純です。標準では、驚きを防ぐために、合成控除ガイドで完全な転送セマンティクスを明確にオフにしました。
LF
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.