짧은 대답 : x
종속 이름 을 만들기 위해 템플릿 매개 변수를 알 때까지 조회가 지연됩니다.
긴 대답 : 컴파일러가 템플릿을 볼 때 템플릿 매개 변수를 보지 않고 즉시 특정 검사를 수행해야합니다. 다른 매개 변수는 매개 변수를 알 때까지 지연됩니다. 이를 2 단계 컴파일이라고하며 MSVC는이를 수행하지 않지만 표준에 필요하며 다른 주요 컴파일러에 의해 구현됩니다. 원하는 경우 컴파일러는 템플릿을 보는 즉시 (일부 내부 구문 분석 트리 표현으로) 템플릿을 컴파일하고 나중에 인스턴스화 컴파일을 연기해야합니다.
템플릿의 특정 인스턴스가 아닌 템플릿 자체에서 수행되는 검사를 수행하려면 컴파일러가 템플릿의 코드 문법을 확인할 수 있어야합니다.
C ++ (및 C)에서 코드 문법을 해결하려면 때로는 무언가가 유형인지 여부를 알아야합니다. 예를 들면 다음과 같습니다.
#if WANT_POINTER
typedef int A;
#else
int A;
#endif
static const int x = 2;
template <typename T> void foo() { A *x = 0; }
A가 유형 인 경우 포인터를 선언합니다 (global을 그림자 화하는 것 외에는 아무런 영향을 미치지 않음 x
). A가 객체이면 곱셈입니다 (그리고 일부 연산자가 과부하를 허용하지 않으면 rvalue에 할당됩니다). 틀린 경우이 오류는 1 단계에서 진단해야하며 , 표준 에 따라 템플릿의 특정 인스턴스화가 아니라 템플릿 의 오류 로 정의됩니다 . 템플릿이 인스턴스화되지 않은 경우에도 A가 int
위의 코드 인 경우 위의 코드가 잘못 작성되어 foo
템플릿이 아니고 일반 기능인 것처럼 진단해야합니다 .
이제 표준에 따르면 템플릿 매개 변수에 의존 하지 않는 이름 은 1 단계에서 확인할 수 있어야합니다. A
여기서는 종속 이름이 아니며 유형에 관계없이 동일한 것을 나타냅니다 T
. 따라서 1 단계에서 템플릿을 찾아서 확인하려면 템플릿을 정의하기 전에 템플릿을 정의해야합니다.
T::A
T에 의존하는 이름이 될 것입니다. 1 단계에서 유형인지 여부를 알 수 없습니다. T
인스턴스화에서 사용되는 유형 은 아직 정의되지 않았으며 템플릿 매개 변수로 사용할 유형을 모르는 경우에도 마찬가지입니다. 그러나 잘못된 형식의 템플릿에 대한 소중한 1 단계 검사를 수행하려면 문법을 해결해야합니다. 따라서 표준에는 종속 이름에 대한 규칙이 있습니다. 컴파일러 는 유형이거나 특정 모호하지 않은 컨텍스트에서 사용 typename
되도록 지정 하지 않는 한 유형이 아닌 것으로 가정해야 합니다. 실시 예에있어 , 기본 클래스로 사용되며, 따라서 모호 타입이다. 데이터 멤버가있는 일부 유형으로 인스턴스화되는 경우template <typename T> struct Foo : T::A {};
T::A
Foo
A
중첩 된 유형 A 대신 템플릿의 오류가 아닌 인스턴스화 (단계 2)를 수행하는 코드의 오류입니다 (단계 1).
그러나 종속 기본 클래스가있는 클래스 템플릿은 어떻습니까?
template <typename T>
struct Foo : Bar<T> {
Foo() { A *x = 0; }
};
A는 종속적 인 이름입니까? 기본 클래스를 사용하면 기본 클래스에 모든 이름이 나타날 수 있습니다. 따라서 A는 종속적 인 이름이라고 말할 수 있고, 이것을 비 유형으로 취급합니다. 이는 Foo의 모든 이름 이 종속적이므로 Foo에 사용 된 모든 유형 (내장 유형 제외)이 자격을 갖추어야하는 바람직하지 않은 효과가 있습니다. Foo 내부에서 다음과 같이 작성해야합니다.
typename std::string s = "hello, world";
때문에이 std::string
종속 이름, 따라서 별도로 명시하지 않는 한 비 형으로 가정한다. 아야!
원하는 코드를 (수와 두 번째 문제는 return x;
) 경우에도이 있다는 것입니다 Bar
전에 정의 Foo
하고, x
그 정의의 구성원이 아닌, 누군가가 이후의 전문화를 정의 할 수있는 Bar
몇 가지 유형에 대해 Baz
, 그러한 Bar<Baz>
데이터 멤버가 않습니다 x
인스턴스화 다음과 Foo<Baz>
. 따라서 해당 인스턴스화에서 템플릿은 global을 반환하는 대신 데이터 멤버를 반환합니다 x
. 의 기본 템플릿 정의가있는 경우 또는 반대로 Bar
했다 x
, 그들은 그것을하지 않고 전문성을 정의 할 수 있습니다, 그리고 템플릿은 글로벌 찾는 것 x
으로 돌아갑니다 Foo<Baz>
. 나는 이것이 당신이 가진 문제만큼 놀랍고 괴로운 것으로 판단되었지만 조용합니다. 놀라운 오류를 일으키는 것과는 반대로 놀라운.
이러한 문제를 피하기 위해 사실상 표준에 따르면 클래스 템플릿의 종속 기본 클래스는 명시 적으로 요청하지 않는 한 검색 대상으로 간주되지 않습니다. 이것은 종속 기반에서 찾을 수 있기 때문에 모든 것이 종속되는 것을 막습니다. 그것은 또한 당신이보고있는 바람직하지 않은 영향을 미칩니다-당신은 기본 클래스에서 물건을 자격이 없거나 찾을 수 없습니다. A
의존적 으로 만드는 세 가지 일반적인 방법이 있습니다 .
using Bar<T>::A;
클래스에서- A
이제에 Bar<T>
의존 하는 것을 의미합니다 .
Bar<T>::A *x = 0;
사용 시점에서-다시 한 번, A
확실 Bar<T>
합니다. 이것은 typename
사용되지 않았으므로 곱셈이므로 나쁜 예 일 수 있지만 operator*(Bar<T>::A, x)
rvalue를 반환 하는지 여부를 확인하려면 인스턴스화가 될 때까지 기다려야합니다 . 누가 알 겠지?
this->A;
사용 시점에서- A
멤버이므로 멤버가 아닌 경우 Foo
기본 클래스에 있어야합니다. 다시 말하면 표준에 따라 달라집니다.
2 단계 컴파일은 까다 롭고 어려우며, 코드의 추가 언어에 대한 놀라운 요구 사항을 소개합니다. 그러나 오히려 민주주의와 마찬가지로 아마도 다른 모든 것들과는 별도로 최악의 일을하는 방법 일 것입니다.
예제 에서 기본 클래스에 중첩 유형 return x;
인지 여부 x
는 합리적이지 않다고 합리적으로 주장 할 수 있으므로 언어는 (a) 종속 이름이라고 말하고 (2) 비 유형으로 취급해야합니다. 귀하의 코드는없이 작동 this->
합니다. 귀하의 경우에는 적용되지 않는 문제에 대한 솔루션의 부수적 피해의 대상이 되겠지만, 여전히 기본 계급이 잠재적으로 그림자가있는 이름을 소개하거나 생각하는 이름이없는 문제가 여전히 있습니다 그들은 가지고 있었고, 대신 세계가 발견되었습니다.
또한 아마도 기본 의존 이름 (어떻게 든 개체로 지정하지 않는 유형을 가정) 또는 기본에서 (보다 상황에 맞는되어야한다는에 대한 반대해야한다고 주장 할 수 std::string s = "";
, std::string
아무것도 다른 문법하지 않습니다 이후 형식으로 읽을 수 std::string *s = 0;
모호 하지만 의미 가 있습니다). 다시 한 번 나는 규칙이 어떻게 합의되었는지 잘 모른다. 내 생각에 필요한 텍스트 페이지 수는 컨텍스트가 유형을 지정하고 유형을 지정하지 않는 많은 특정 규칙을 만드는 것에 대해 완화됩니다.