이 템플릿 기능이 예상대로 작동하지 않는 이유는 무엇입니까? std::cout << typeid(val).name() << “

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

#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 섹션에 설명 된대로) 동일한 인스턴스를 자동으로 수행하지 않아야 합니까?



답변

이름 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)예제와 일치 하여 출력이됩니다 .


답변

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

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

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

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

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

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

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

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

이것은 말이됩니까?