C ++에서 형식 정보는 언제 역방향으로 흐르나요?


92

방금 Stephan T. Lavavej CppCon 2018가 "Class Template Argument Deduction (클래스 템플릿 인수 추론)"에 대해 이야기하는 것을 보았습니다. 어느 시점에서 그는 우연히 다음과 같이 말합니다.

C ++에서 유형 정보는 거의 역방향으로 흐르지 않습니다. 한두 가지 경우가 있기 때문에 "거의"라고 말해야했습니다 .

그가 어떤 사건을 언급하고 있는지 알아 내려고했지만 나는 아무것도 생각할 수 없었다. 따라서 질문 :

어떤 경우에 C ++ 17 표준이 형식 정보가 역방향으로 전파되도록 요구합니까?


패턴 매칭 부분 전문화 및 구조 분해 할당.
v.oddou

답변:


80

다음은 하나 이상의 경우입니다.

struct foo {
  template<class T>
  operator T() const {
    std::cout << sizeof(T) << "\n";
    return {};
  }
};

당신이 할 경우 foo f; int x = f; double y = f;, 형식 정보를 알아낼 "뒤로"를 흐를 것 T입니다 operator T.

이를보다 고급 방식으로 사용할 수 있습니다.

template<class T>
struct tag_t {using type=T;};

template<class F>
struct deduce_return_t {
  F f;
  template<class T>
  operator T()&&{ return std::forward<F>(f)(tag_t<T>{}); }
};
template<class F>
deduce_return_t(F&&)->deduce_return_t<F>;

template<class...Args>
auto construct_from( Args&&... args ) {
  return deduce_return_t{ [&](auto ret){
    using R=typename decltype(ret)::type;
    return R{ std::forward<Args>(args)... };
  }};
}

이제 할 수 있습니다

std::vector<int> v = construct_from( 1, 2, 3 );

그리고 그것은 작동합니다.

물론, 그냥하지 않는 이유는 {1,2,3}무엇입니까? 음, {1,2,3}표현이 아닙니다.

std::vector<std::vector<int>> v;
v.emplace_back( construct_from(1,2,3) );

사실, 좀 더 마법이 필요합니다 : Live example . (나는 추론 반환을 F의 SFINAE 검사를 수행하고 F를 SFINAE 친화적으로 만들고 deduce_return_t 연산자 T에서 std :: initializer_list를 차단해야합니다.)


매우 흥미로운 답변이었고 새로운 트릭을 배웠으므로 대단히 감사합니다! 예제를 컴파일 하기 위해 템플릿 추론 지침을 추가 해야 했지만 그 외에는 매력처럼 작동합니다!
Massimiliano

5
&&예선 operator T()은 큰 감동입니다. 여기서를 잘못 사용 auto하면 컴파일 오류가 발생 하여 잘못된 상호 작용을 피할 수 auto있습니다.
Justin

1
매우 인상적입니다. 예제의 아이디어에 대한 참조 / 대화를 알려 주시겠습니까? 또는 어쩌면 ... :) 원래의
llllllllll

3
@lili 어떤 아이디어? 나는 계산 5 : 연산자 T를 사용하여 반환 유형을 추론합니까? 추론 된 유형을 람다에 전달하기 위해 태그를 사용합니까? 변환 연산자를 사용하여 직접 배치 개체를 구성 하시겠습니까? 4 개를 모두 연결 하시겠습니까?
Yakk-Adam Nevraumont

1
@lili Tha "보다 진보 된 방법"의 예는 내가 말했듯이 단지 4 개 정도의 아이디어가 서로 붙어 있습니다. 나는이 포스트를 위해 즉석에서 접착을했지만, 나는 확실히 함께 사용 된 것의 많은 쌍 또는 심지어 세 쌍둥이를 보았다. 그것은 (tootsie가 불평하는 것처럼) 합리적으로 모호한 기술이지만 새로운 것은 아닙니다.
Yakk-Adam Nevraumont

31

Stephan T. Lavavej는 그가 트윗에서 이야기하고있는 사건을 설명했습니다 :

내가 생각했던 경우는 오버로드 / 템플릿 함수의 주소를 가져올 수 있고 특정 유형의 변수를 초기화하는 데 사용되는 경우 원하는 변수를 명확하게 할 수있는 곳입니다. (모호하지 않은 목록이 있습니다.)

오버로드 된 함수 주소의 cppreference 페이지 에서 이에 대한 예를 볼 수 있습니다. 아래 몇 가지를 제외했습니다.

int f(int) { return 1; } 
int f(double) { return 2; }   

void g( int(&f1)(int), int(*f2)(double) ) {}

int main(){
    g(f, f); // selects int f(int) for the 1st argument
             // and int f(double) for the second

     auto foo = []() -> int (*)(int) {
        return f; // selects int f(int)
    }; 

    auto p = static_cast<int(*)(int)>(f); // selects int f(int)
}

Michael Park는 다음과 같이 덧붙입니다 .

구체적인 유형을 초기화하는 것에도 국한되지 않습니다. 또한 인수의 수만으로 추론 할 수 있습니다.

이 라이브 예제를 제공 합니다 .

void overload(int, int) {}
void overload(int, int, int) {}

template <typename T1, typename T2,
          typename A1, typename A2>
void f(void (*)(T1, T2), A1&&, A2&&) {}

template <typename T1, typename T2, typename T3,
          typename A1, typename A2, typename A3>
void f(void (*)(T1, T2, T3), A1&&, A2&&, A3&&) {}

int main () {
  f(&overload, 1, 2);
}

여기서 자세히 설명 하겠습니다 .


4
우리는 이것을 다음과 같이 설명 할 수도 있습니다 : 표현의 유형이 문맥에 의존하는 경우?
MM

20

오버로드 된 함수의 정적 캐스팅은 흐름이 일반적인 오버로드 해결과 반대 방향으로 이동한다고 믿습니다. 그래서 그 중 하나는 거꾸로 된 것 같습니다.


7
나는 이것이 옳다고 믿는다. 그리고 함수 포인터 유형에 함수 이름을 전달할 때입니다. 유형 정보는 표현식의 컨텍스트 (할당하는 유형 / 구성하는 유형)에서 함수의 이름으로 역방향으로 흘러 어떤 오버로드가 선택되었는지 결정합니다.
Yakk-Adam Nevraumont
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.