Foo <T> :: Foo (T &&) 호출에서 C ++가 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&&것은 간단한 rvalue 참조이고 사용자 정의 경우 전달 참조 입니다. 이것은 C ++ 17 표준의 [temp.deduct.call] / 3 (초안 N4659, 광산 강조)로 지정됩니다.

전달 참조 이력서-비정규 템플릿 파라미터 r- 수치로 기준 인 클래스 템플릿의 템플릿 매개 변수를 나타내지 않는 (클래스 템플릿 인수 공제 동안 ([over.match.class.deduct])).

따라서 클래스 생성자에서 합성 된 후보 추론하지 않을 것이다 T(추론 할 수있는 착신 기준에서 마치 T그것이 있도록하는 좌변 기준으로 T&&만 추론 것 대신에도 좌변 기준 임)하지만, T그 있도록 비 참조로 T&&항상 rvalue 참조


1
명확하고 간결한 답변에 감사드립니다. 참조 전달 규칙에 클래스 템플릿 매개 변수에 예외가있는 이유 를 알고 있습니까?
jtbandes

2
@jtbandes 이것은 미국 국가 기관의 의견에 따라 변경된 것으로 보입니다 (서류 p0512r0 참조) . 그래도 댓글을 찾을 수 없습니다. 이론적 근거는 rvalue 참조를 사용하는 생성자를 작성하는 경우 일반적으로 a를 지정 Foo<int>(...)하든 그냥 Foo(...)전달 하든 참조를 전달하는 경우 (예 : Foo<int&>대신 추론 할 수 있음)와 동일한 방식으로 작동 할 것으로 예상한다는 것입니다.
walnut

6

때문에 여기에 문제는, 그입니다 클래스 에 템플릿한다 T생성자에서, Foo(T&&)우리는 하지 형태 추론을 수행하는 단계; 항상 r- 값 참조가 있습니다. 즉,의 생성자는 Foo실제로 다음과 같습니다.

Foo(int&&)

Foo(2)2prvalue 이기 때문에 작동합니다 .

Foo(x)x바인딩 할 수없는 lvalue 가 아니기 때문 입니다 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&&)rvalue-reference 와 같이 보입니다 . 따라서 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


두 번째 수준의 템플릿은 필요하지 않은 것 같습니다. 어떤 가치를 제공합니까? T &&가 항상 rvalue 참조로 취급되는 이유 를 설명하는 일부 문서에 대한 링크를 제공 할 수 있습니까 ?
jtbandes

1
그러나 T가 아직 추론되지 않았다면 "T로 변환 가능한 모든 유형"은 무엇을 의미합니까?
jtbandes

1
해결 방법을 신속하게 제공 할 수 있지만 어떤 식 으로든 수정하지 않으면 작동하지 않는 이유에 대한 설명에 더 집중할 수 있습니까? 왜 그대로 작동하지 않습니까? " x는 바인딩 할 수없는 lvalue int&&이지만"이해하지 못하는 사람 Foo<int&>(x)은 작동 할 수 있지만 자동으로 파악되지 않은 당황 할 것입니다. 우리 모두는 왜 그 이유에 대해 더 깊이 이해하고 싶다고 생각합니다.
Wyck

2
@ Wyck : 나는 그 이유에 더 초점을 맞추기 위해 게시물을 업데이트했습니다.
AndyG

3
@Wyck 실제 이유는 매우 간단합니다. 표준 놀라움을 방지하기 위해 합성 된 추론 가이드에서 완벽한 전달 의미를 구체적으로 해제했습니다 .
LF
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.