이 템플릿 기능이 예상대로 작동하지 않는 이유는 무엇입니까?


23

템플릿 기능에 대해 읽고 있었고이 문제로 혼란스러워했습니다.

#include <iostream>

void f(int) {
    std::cout << "f(int)\n";
}

template<typename T>
void g(T val) {
    std::cout << typeid(val).name() << "  ";
    f(val);
}

void f(double) {
    std::cout << "f(double)\n";
}

template void g<double>(double);

int main() {
    f(1.0); // f(double)
    f(1);   // f(int)
    g(1.0); // d  f(int), this is surprising
    g(1);   // i  f(int)
}

내가 쓰지 않으면 결과는 같습니다 template void g<double>(double);.

나는 g<double>이후 f(double)에 인스턴스화해야 한다고 생각 하므로 fin g호출은 호출해야합니다 f(double). 놀랍게도, 그것은 여전히 호출 f(int)g<double>. 누구든지 이것을 이해하도록 도울 수 있습니까?


답을 읽은 후 혼란이 무엇인지 알아 냈습니다.

다음은 업데이트 된 예입니다. 다음에 대한 전문화를 추가 한 것을 제외하고는 대부분 변경되지 않았습니다 g<double>.

#include <iostream>

void f(int){cout << "f(int)" << endl;}

template<typename T>
void g(T val)
{
    cout << typeid(val).name() << "  ";
    f(val);
}

void f(double){cout << "f(double)" << endl;}

//Now use user specialization to replace
//template void g<double>(double);

template<>
void g<double>(double val)
{
    cout << typeid(val).name() << "  ";
    f(val);
}

int main() {
    f(1.0); // f(double)
    f(1);  // f(int)
    g(1.0); // now d  f(double)
    g(1);  // i  f(int)
}

사용자 전문화 g(1.0)로 예상대로 동작합니다.

컴파일러 g<double>가 동일한 위치에서 (또는 C ++ 프로그래밍 언어 , 제 4 판 main()26.3.3 섹션에 설명 된대로) 동일한 인스턴스를 자동으로 수행하지 않아야 합니까?


3
마지막 전화 g(1)i f(int)나를 위해 준다. 당신은 썼습니다 d f(double). 오타입니까?
HTNW

예. 죄송합니다. 업데이트 됨
Zhongqi Cheng

템플릿의 기본 원칙은 사용자가 선언 한 기호에 의한 내부 라이브러리 호출의 하이재킹을 방지하면서 사용자 유형에 대한 작업 사용을 지원하는 것입니다. 템플릿에 대한 "개념"계약이 없기 때문에 불가능한 타협으로, 이러한 "계약"을 소개하기에는 너무 늦습니다.
curiousguy

답변:


12

이름 f은 종속 이름 이며 ( T인수 를 통해 결정됨 val) 두 단계 로 분석됩니다 .

  1. 비 ADL 조회 는 템플릿 정의 컨텍스트 에서 볼 수있는 함수 선언을 검사합니다 .
  2. ADL은 템플릿 정의 컨텍스트 또는 템플릿 인스턴스화 컨텍스트 에서 볼 수있는 함수 선언을 검사합니다 .

void f(double)템플릿 정의 컨텍스트에서 볼 수없고, ADL 중 하나를 찾을 수 없습니다 때문에

기본 유형의 인수의 경우 연관된 네임 스페이스 및 클래스 세트가 비어 있습니다.


예제를 약간 수정할 수 있습니다.

struct Int {};
struct Double : Int {};

void f(Int) { 
    std::cout << "f(Int)";
}

template<typename T>
void g(T val) {
    std::cout << typeid(val).name() << ' ';
    f(val);
    // (f)(val);
}

void f(Double) { 
    std::cout << "f(Double)";
}

int main() {
    g(Double{});
}

이제 ADL은 void f(Double)두 번째 단계에서 찾을 수 있으며 출력은입니다 6Double f(Double). 대신 (f)(val)(또는 ::f(val))를 쓰면 ADL을 비활성화 할 수 있습니다 f(val). 그런 다음 6Double f(Int)예제와 일치 하여 출력이됩니다 .


대단히 감사합니다. g <double>의 인스턴스화가 코드에서 어디에 있는지 궁금합니다. main () 직전입니까? 그렇다면 인스턴스화 된 g <double> 정의가 f (int)와 f (double)을 모두 볼 수 없어야하고 마지막으로 f (double)을 선택해서는 안됩니까?
Zhongqi Cheng

@ZhongqiCheng 1 단계에서 템플리트 정의 컨텍스트 만 고려되며 해당 컨텍스트에서 void f(double)표시되지 않습니다.이 컨텍스트는 선언 전에 종료됩니다. 2 단계에서 ADL은 아무것도 찾지 않으므로 템플릿 인스턴스화 컨텍스트는 여기서 아무런 역할을하지 않습니다.
평균

@ZhongqiCheng, 당신의 편집에서 당신은 이후에 정의를 도입 void f(double)했습니다. 이제는 f종속적 인 이름이 아닙니다. f(val);의 정의 후에 선언 된 항목과 더 일치 g<double>하는 항목이 없으면 찾을 수 없습니다. "예측"하는 유일한 방법은 ADL (또는 2 단계 조회를 올바르게 구현하지 않는 일부 오래된 컴파일러)입니다.
평균

여기 당신의 대답에 대한 이해가 있습니다. 함수 템플릿 (g <int> 및 g <double>)이 템플릿 정의 직후 인스턴스화된다고 가정해야합니다. 따라서 f (double)는 보이지 않습니다. 이 올바른지. 정말 고맙습니다.
Zhongqi Cheng 1

@ZhongqiCheng, 바로 전에 인스턴스화 main(). 그들은 표시되지 않습니다 f(double)조회의 위상 하나는 아직되지 않은 그것은 더 발견했다 : 인스턴스가 발생했을 때, 너무 늦기 때문에 f(double).
Evg

6

문제가 f(double)발생한 지점에서 문제가 선언되지 않았습니다. 선언을의 앞에 옮기면 template g호출됩니다.

편집 : 왜 수동 인스턴스화를 사용합니까?

(함수 템플릿에 대해서만 이야기하겠습니다. 클래스 템플릿에 대한 유사한 주장도 마찬가지입니다.) 주된 용도는 컴파일 시간을 줄이거 나 템플릿 코드를 사용자에게 숨기는 것입니다.

C ++ 프로그램은 컴파일과 링크의 2 단계로 바이너리에 내장되어 있습니다. 함수 호출을 성공적으로 컴파일하려면 함수 헤더 만 필요합니다. 연결에 성공하려면 컴파일 된 함수 본문을 포함하는 오브젝트 파일이 필요합니다.

이제 컴파일러가 템플릿 함수 호출을 볼 때 템플릿 본문을 아는지 또는 헤더 만 아는지에 따라 달라집니다. 헤더 만 보이면 함수가 템플릿 화되지 않은 것과 동일한 기능을 수행합니다. 링커 호출에 대한 정보를 객체 파일에 넣습니다. 그러나 템플릿의 본문을 보면 또 다른 일이 있습니다. 본문의 적절한 인스턴스를 인스턴스화 하고이 본문을 컴파일하여 객체 파일에도 넣습니다.

여러 소스 파일이 템플릿 함수의 동일한 인스턴스를 호출하는 경우 각 객체 파일에는 함수 인스턴스의 컴파일 된 버전이 포함됩니다. 링커는 이에 대해 알고 단일 컴파일 된 함수에 대한 모든 호출을 해결하므로 프로그램 / 라이브러리의 최종 바이너리에는 하나만 있습니다. 그러나 각 소스 파일을 컴파일하려면 함수를 인스턴스화하고 컴파일 시간이 걸렸습니다.

함수 본문이 하나의 객체 파일에 있으면 링커가 작업을 수행하는 것으로 충분합니다. 소스 파일에서 템플릿을 수동으로 인스턴스화하는 것은 컴파일러가 함수 본문을 해당 소스 파일의 객체 파일에 넣도록하는 방법입니다. (이것은 마치 함수가 호출 된 것처럼 보이지만 인스턴스화는 함수 호출이 유효하지 않은 곳에 작성됩니다.) 이것이 완료되면 함수를 호출하는 모든 파일은 함수의 헤더 만 알고 컴파일 할 수 있습니다 각 호출로 함수 본문을 인스턴스화하고 컴파일하는 데 걸리는 시간이 절약됩니다.

두 번째 이유 (구현 숨기기)가 이제 의미가있을 수 있습니다. 라이브러리 작성자가 템플리트 함수의 사용자가 함수를 사용할 수있게하려면 일반적으로 템플리트 코드를 제공하여 스스로 컴파일 할 수 있도록합니다. 템플릿의 소스 코드를 비밀로 유지하려면 라이브러리를 빌드하는 데 사용하는 코드에서 템플릿을 수동으로 인스턴스화하고 소스 대신 얻은 객체 버전을 사용자에게 제공 할 수 있습니다.

이것은 말이됩니까?


저자의 첫 번째 코드에서 제시된 인스턴스화와 편집 후 저자의 두 번째 코드의 전문화의 차이점을 설명 할 수 있다면 정말 감사합니다. 나는 전문화와 인스턴스화 및 서적에 대한 cppreference 사이트를 여러 번 읽었지만 이해하지 못했습니다. 감사합니다
Dev

@Dev : 질문을 조금 더 지정하십시오. 무엇을 대답 해야할지 모르겠습니다. 기본적으로이 경우 차이점은 특수화가 있으면 컴파일러가이를 사용하지만 존재하지 않는 경우 컴파일러가 템플릿을 가져 와서 인스턴스를 생성하고이 생성 된 인스턴스를 사용한다는 점입니다. 위의 코드에서 전문화와 템플릿 인스턴스는 모두 동일한 코드로 이어집니다.
AshleyWilkes '12

내 질문은 정확히 코드의 한 부분입니다. "template void g <double> (double);" 알고 있다면 프로그래밍 템플릿에서 인스턴스화라는 이름이 붙습니다. 전문화는 저자가 "template <> void g <double> (double val) {cout << typeid (val) .name () <<" "; f ( val);} "차이점을 설명해 주시겠습니까?
Dev

@Dev 나는 이미 그렇게하려고 노력했다. 컴파일러는 가능하다면 전문화를 사용한다. 전문화를 볼 수없는 경우 (예 : 없음) 컴파일러는 템플릿의 인스턴스를 생성하고 해당 인스턴스를 사용합니다. 위의 코드에서 템플릿과 특수화는 모두 동일한 결과를 가져 오므로 컴파일러가 해당 결과를 얻는 방법에 차이가 있습니다. 다른 경우 전문화에는 구현이 포함될 수 있지만 템플릿과 공통 인 것은 없지만 메소드 헤더에는 적용되지 않습니다. 더 깨끗해?
AshleyWilkes '12

1
template void g<double>(double);너무 수동 인스턴스 (주 호출되는 template구문의 구별 기능입니다 못한 각도 브래킷,); 컴파일러에게 메소드의 인스턴스를 작성하도록 지시합니다. 여기서 효과가 없다면 컴파일러는 인스턴스가 호출되는 위치에서 인스턴스를 생성합니다. 수동 인스턴스화는 거의 사용되지 않는 기능입니다. 이제 상황이 더 명확
하다는
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.