this 포인터를 통해 템플릿 기본 클래스 멤버에 액세스해야하는 이유는 무엇입니까?


199

클래스 아래에 있다면하지 단순히 가질 수 템플릿 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;
}

1
아 이런 이름 조회와 관련이 있습니다. 누군가가 이것에 빨리 대답하지 않으면, 그것을 찾아서 게시 할 것입니다 (지금 바쁘다).
templatetypedef

@Ed Swangren : 죄송합니다,이 질문을 게시 할 때 제공된 답변 중에서 누락되었습니다. 나는 그 전에 오랫동안 대답을 찾고있었습니다.
Ali

6
이는 2 단계 이름 조회 (모든 컴파일러가 기본적으로 사용하지는 않음) 및 종속 이름으로 인해 발생합니다. 접두어 이외의이 문제에 대한 3 해결책이 있습니다 x으로 this->: 즉, 1) 접두사를 사용 base<T>::x, 2) 성명을 추가 using base<T>::x, 3) 허용 모드를 가능하게하는 글로벌 컴파일러 스위치를 사용. 이 솔루션의 장단점은 stackoverflow.com/questions/50321788/…에
George Robinson

답변:


274

짧은 대답 : 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::AT에 의존하는 이름이 될 것입니다. 1 단계에서 유형인지 여부를 알 수 없습니다. T인스턴스화에서 사용되는 유형 은 아직 정의되지 않았으며 템플릿 매개 변수로 사용할 유형을 모르는 경우에도 마찬가지입니다. 그러나 잘못된 형식의 템플릿에 대한 소중한 1 단계 검사를 수행하려면 문법을 해결해야합니다. 따라서 표준에는 종속 이름에 대한 규칙이 있습니다. 컴파일러 유형이거나 특정 모호하지 않은 컨텍스트에서 사용 typename되도록 지정 하지 않는 한 유형이 아닌 것으로 가정해야 합니다. 실시 예에있어 , 기본 클래스로 사용되며, 따라서 모호 타입이다. 데이터 멤버가있는 일부 유형으로 인스턴스화되는 경우template <typename T> struct Foo : T::A {};T::AFooA 중첩 된 유형 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;모호 하지만 의미 가 있습니다). 다시 한 번 나는 규칙이 어떻게 합의되었는지 잘 모른다. 내 생각에 필요한 텍스트 페이지 수는 컨텍스트가 유형을 지정하고 유형을 지정하지 않는 많은 특정 규칙을 만드는 것에 대해 완화됩니다.


1
오, 자세한 답변입니다. 내가 한번도 귀찮게 해본 적이없는 몇 가지 사항을 설명했다. :) +1
jalf

20
@jalf : C ++ QTWBFAETYNSYEWTKTAAHMITTBGOW- "답변을 알고 싶어하고 더 중요한 일이 있다는 것을 제외하고 자주 묻는 질문"이 있습니까?
Steve Jessop

4
특별한 대답은 질문이 FAQ에 적합한 지 궁금합니다.
Matthieu M.

우와, 백과 사전이라고 할 수 있습니까? highfive 하나의 미묘한 점 : "Foo가 중첩 유형 A 대신 데이터 멤버 A가있는 일부 유형으로 인스턴스화되는 경우 템플릿의 오류가 아닌 인스턴스화 (단계 2)를 수행하는 코드 오류입니다 (단계 1)." 템플릿의 형식이 잘못되었다고 말하는 것이 더 좋을 수도 있지만 템플릿 작성기에서 잘못된 가정이나 논리 버그가 발생한 경우 일 수 있습니다. 플래그가 지정된 인스턴스화가 실제로 사용 된 유스 케이스 인 경우 템플리트가 올바르지 않습니다.
Ionoclast Brigham

1
@JohnH. 여러 개의 컴파일러가 구현 -fpermissive되거나 유사한 것을 감안할 때 가능합니다. 구현 방법에 대한 세부 정보는 모르지만 x실제 tempate 기본 클래스를 알 때까지 컴파일러는 해결을 연기해야합니다 T. 따라서 비 허용 모드에서는 원칙적으로 지연된 사실을 기록하고, 연기하고, 일단 T조회하면되며, 조회가 성공하면 제안한 텍스트를 발행 할 수 있습니다. 그것이 작동하는 경우에만 만들어지면 매우 정확한 제안 일 것입니다 : 사용자 x가 다른 범위에서 다른 것을 의미했을 가능성 은 매우 작습니다!
Steve Jessop

13

(2011 년 1 월 10 일의 원본 답변)

대답을 찾았습니다. GCC 문제 : 템플릿 인수에 의존하는 기본 클래스 멤버 사용 . 대답은 gcc에만 국한되지 않습니다.


업데이트 : C ++ 11 표준의 N3337 초안 에서 mmichael의 의견에 대한 답변 :

14.6.2 종속 이름 [temp.dep]
[...]
3 클래스 또는 클래스 템플릿의 정의에서 기본 클래스가 템플릿 매개 변수에 의존하는 경우 정규화되지 않은 이름 조회 중에 기본 클래스 범위가 검사되지 않습니다. 클래스 템플릿 또는 멤버의 정의 지점 또는 클래스 템플릿 또는 멤버의 인스턴스화 시점

"표준이 말했기 때문에" 가 답으로 계산 되는지 여부 모르겠습니다. 우리는 표준이 왜 표준을 요구하는지 물어볼 수 있지만 Steve Jessop의 탁월한 답변 과 다른 사람들이 지적한 것처럼이 후자의 질문에 대한 대답은 다소 길고 논쟁의 여지가 있습니다. 불행히도 C ++ 표준에 관해서는 표준이 왜 무언가를 요구하는지에 대한 짧고 독립적 인 설명을하는 것이 거의 불가능합니다. 이것은 후자의 질문에도 적용됩니다.


11

x상속 동안 숨겨져 있습니다. 다음을 통해 숨기기를 해제 할 수 있습니다.

template <typename T>
class derived : public base<T> {

public:
    using base<T>::x;             // added "using" statement
    int f() { return x; }
};

25
이 답변은 숨겨져 있는지 설명하지 않습니다 .
jamesdlin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.