this 포인터를 통해 템플릿 기본 클래스 멤버에 액세스해야하는 이유는 무엇입니까?
클래스 아래에 있다면하지 단순히 가질 수 템플릿 x
에서 derived
클래스입니다. 그러나 아래 코드에서는 을 사용해야 this->x
합니다. 왜?
template <typename T>
class base {
protected:
int x;
};
template <typename T>
class derived : public base<T> {
public:
int f() { return this->x; }
};
int main() {
derived<int> d;
d.f();
return 0;
}
짧은 대답 : 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
종속 이름, 따라서 별도로 명시하지 않는 한 비 형으로 가정한다. 아야!
A second problem with allowing your preferred code (return x;
) is that even if Bar
is defined before Foo
, and x
isn't a member in that definition, someone could later define a specialization of Bar
for some type Baz
, such that Bar<Baz>
does have a data member x
, and then instantiate Foo<Baz>
. So in that instantiation, your template would return the data member instead of returning the global x
. Or conversely if the base template definition of Bar
had x
, they could define a specialization without it, and your template would look for a global x
to return in Foo<Baz>
. I think this was judged to be just as surprising and distressing as the problem you have, but it's silently surprising, as opposed to throwing a surprising error.
To avoid these problems, the standard in effect says that dependent base classes of class templates just aren't considered for search unless explicitly requested. This stops everything from being dependent just because it could be found in a dependent base. It also has the undesirable effect that you're seeing - you have to qualify stuff from the base class or it's not found. There are three common ways to make A
dependent:
using Bar<T>::A;
in the class -A
now refers to something inBar<T>
, hence dependent.Bar<T>::A *x = 0;
at point of use - Again,A
is definitely inBar<T>
. This is multiplication sincetypename
wasn't used, so possibly a bad example, but we'll have to wait until instantiation to find out whetheroperator*(Bar<T>::A, x)
returns an rvalue. Who knows, maybe it does...this->A;
at point of use -A
is a member, so if it's not inFoo
, it must be in the base class, again the standard says this makes it dependent.
Two-phase compilation is fiddly and difficult, and introduces some surprising requirements for extra verbiage in your code. But rather like democracy it's probably the worst possible way of doing things, apart from all the others.
You could reasonably argue that in your example, return x;
doesn't make sense if x
is a nested type in the base class, so the language should (a) say that it's a dependent name and (2) treat it as a non-type, and your code would work without this->
. To an extent you're the victim of collateral damage from the solution to a problem that doesn't apply in your case, but there's still the issue of your base class potentially introducing names under you that shadow globals, or not having names you thought they had, and a global being found instead.
You could also possibly argue that the default should be the opposite for dependent names (assume type unless somehow specified to be an object), or that the default should be more context sensitive (in std::string s = "";
, std::string
could be read as a type since nothing else makes grammatical sense, even though std::string *s = 0;
is ambiguous). Again, I don't know quite how the rules were agreed. My guess is that the number of pages of text that would be required, mitigated against creating a lot of specific rules for which contexts take a type and which a non-type.
(Original answer from Jan 10, 2011)
I think I have found the answer: GCC issue: using a member of a base class that depends on a template argument. The answer is not specific to gcc.
Update: In response to mmichael's comment, from the draft N3337 of the C++11 Standard:
14.6.2 Dependent names [temp.dep]
[...]
3 In the definition of a class or class template, if a base class depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.
Whether "because the standard says so" counts as an answer, I don't know. We can now ask why the standard mandates that but as Steve Jessop's excellent answer and others point out, the answer to this latter question is rather long and arguable. Unfortunately, when it comes to the C++ Standard, it is often nearly impossible to give a short and self-contained explanation as to why the standard mandates something; this applies to the latter question as well.
The x
is hidden during the inheritance. You can unhide via:
template <typename T>
class derived : public base<T> {
public:
using base<T>::x; // added "using" statement
int f() { return x; }
};
'IT' 카테고리의 다른 글
'setdefault'dict 메소드의 사용 사례 (0) | 2020.05.20 |
---|---|
뒤로 버튼을 클릭 할 때 크로스 브라우저 onload 이벤트가 있습니까? (0) | 2020.05.20 |
클래스 객체에 대한 사용자 정의 문자열 표현을 만드는 방법은 무엇입니까? (0) | 2020.05.20 |
Maven 프로젝트를 Eclipse로 가져 오기 (0) | 2020.05.20 |
문서로 해석되었지만 MIME 유형 application / zip으로 전송 된 자원 (0) | 2020.05.20 |