“템플릿”및“typename”키워드를 어디에 그리고 왜 넣어야합니까?


1125

템플릿에서, 어디서, 왜 넣어해야합니까 typenametemplate의존 이름을?
어쨌든 종속 이름은 정확히 무엇입니까?

다음 코드가 있습니다.

template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        // Q: where to add typename/template here?
        typedef Tail::inUnion<U> dummy; 
    };
    template< > struct inUnion<T> {
    };
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
    // ...
    template<typename U> struct inUnion {
        char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
    };
    template< > struct inUnion<T> {
    };
};

내가 가진 문제는 typedef Tail::inUnion<U> dummy라인에 있습니다. 나는 그것이 inUnion의존적 인 이름 이라고 확신하며 , VC ++는 질식에 아주 적합합니다.
또한 template컴파일러에 inUnion이 템플릿 ID라는 것을 알리기 위해 어딘가에 추가 할 수 있어야한다는 것을 알고 있습니다 . 그러나 정확히 어디에? 그런 다음 inUnion이 클래스 템플릿이라고 가정해야합니다. 즉 inUnion<U>함수가 아닌 유형의 이름을 지정합니까?


1
성가신 질문 : 왜 부스트 : : Variant?
Assaf Lavie

58
정치적 민감성, 이식성.
MSalters

5
마지막 질문과 코드를 처음에 배치하여 실제 질문 ( "템플릿 / 유형 이름을 어디에 넣을까요?")을 더 잘 보이게하고 1024x 화면에 맞게 코드를 가로로 줄였습니다.
Johannes Schaub-litb

7
"typename"과 "template"에 대해 궁금해하는 대부분의 사람들이 "종속 이름"이 무엇인지 모르기 때문에 제목에서 "종속 이름"을 제거했습니다. 이 방법으로 혼동을 줄이십시오.
Johannes Schaub-litb

2
@MSalters : 부스트는 상당히 이식 가능합니다. 나는 정치 만이 부스트가 종종 포용되는 일반적인 이유라고 말합니다. 내가 아는 유일한 이유는 빌드 시간이 길어지기 때문입니다. 그렇지 않으면 이것은 바퀴를 재발 명하는 수천 달러를 잃는 것입니다.
v.oddou

답변:


1163

( 내 C ++ 11 답변도 여기를 참조 하십시오 )

C ++ 프로그램을 구문 분석하려면 컴파일러는 특정 이름이 유형인지 여부를 알아야합니다. 다음 예제는이를 보여줍니다.

t * f;

이것을 어떻게 파싱해야합니까? 많은 언어에서 컴파일러는 코드 행을 구문 분석하고 기본적으로 수행하는 작업을 이해하기 위해 이름의 의미를 알 필요가 없습니다. 그러나 C ++에서 위의 내용 t은 의미 에 따라 크게 다른 해석을 생성 할 수 있습니다 . 타입이라면 포인터 선언이 될 것입니다 f. 그러나 유형이 아닌 경우 곱셈이됩니다. 따라서 C ++ 표준은 (3/7) 단락에서 말합니다.

일부 이름은 유형 또는 템플릿을 나타냅니다. 일반적으로 이름이 발견 될 때마다 해당 이름이 포함 된 프로그램을 계속 구문 분석하기 전에 해당 이름이 이러한 엔티티 중 하나를 나타내는 지 여부를 판별해야합니다. 이를 결정하는 프로세스를 이름 조회라고합니다.

템플릿 유형 매개 변수를 참조하는 t::x경우 컴파일러는 이름이 무엇을 의미 하는지 어떻게 알 수 t있습니까? x는 곱할 수 있거나 선언을 생성 할 수있는 중첩 클래스 또는 typedef 일 수있는 정적 int 데이터 멤버 일 수 있습니다. 이름에이 속성이있는 경우 실제 템플릿 인수를 알 때까지 조회 할 수없는 경우 해당 이름을 종속 이름 이라고합니다 (템플릿 매개 변수에 "의존").

사용자가 템플릿을 인스턴스화 할 때까지 기다리는 것이 좋습니다.

사용자가 템플릿을 인스턴스화 할 때까지 기다렸다가 나중에의 실제 의미를 찾으십시오 t::x * f;.

이것은 작동하며 실제로 표준에서 가능한 구현 접근 방식으로 허용됩니다. 이 컴파일러는 기본적으로 템플릿의 텍스트를 내부 버퍼에 복사하고 인스턴스화가 필요한 경우에만 템플릿을 구문 분석하고 정의에서 오류를 감지합니다. 그러나 템플릿 제작자가 만든 오류로 템플릿 사용자 (가난한 동료)를 괴롭히는 대신, 다른 구현에서는 인스턴스화가 발생하기 전에 템플릿을 조기에 확인하고 가능한 한 빨리 정의 오류를 발생시킵니다.

따라서 컴파일러에게 특정 이름은 유형이고 특정 이름은 그렇지 않다고 알려주는 방법이 있어야합니다.

"typename"키워드

답은 : 우리 는 컴파일러가 이것을 어떻게 파싱해야하는지 결정합니다. 경우 t::x종속 이름입니다, 우리는하여 접두사해야 할 typename어떤 방법으로 그것을 구문 분석하는 컴파일러를 말해. 표준은 (14.6 / 2)에서 말합니다.

템플리트 선언 또는 정의에 사용되며 템플리트 매개 변수에 종속 된 이름은 적용 가능한 이름 검색에서 유형 이름을 찾거나 이름이 키워드 typename에 의해 규정되지 않는 한 유형의 이름을 지정하지 않는 것으로 간주됩니다.

typename컴파일러는 템플릿 정의에서 적용 가능한 이름 조회를 사용하여 구문 자체를 구문 분석하는 방법 (예 : 유형 템플릿 매개 변수 T *f;when 인 경우 T) 을 알아낼 수 있으므로 많은 이름 이 필요하지 않습니다 . 그러나 t::x * f;선언이 되려면로 작성해야합니다 typename t::x *f;. 키워드를 생략하고 이름을 유형이 아닌 것으로 간주하지만 인스턴스화가 발견되면 유형을 나타내는 경우 일반적인 오류 메시지가 컴파일러에서 생성됩니다. 때때로 정의 시간에 오류가 발생합니다.

// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;

구문은 typename규정 된 이름 앞에만 허용 됩니다. 규정되지 않은 이름은 항상 유형을 참조하는 것으로 알려져 있습니다.

소개 텍스트에서 알 수 있듯이 템플릿을 나타내는 이름에도 비슷한 문제가 있습니다.

"템플릿"키워드

위의 초기 인용문과 표준에 템플릿을 어떻게 처리해야하는지 기억하십니까? 다음과 같은 순진한 예를 보자.

boost::function< int() > f;

인간에게는 분명해 보일 수 있습니다. 컴파일러에게는 그렇지 않습니다. boost::function및 의 다음과 같은 임의의 정의를 상상해보십시오 f.

namespace boost { int function = 0; }
int main() { 
  int f = 0;
  boost::function< int() > f; 
}

실제로 유효한 표현입니다 ! 이 비교 연산자보다 적게 사용하여 boost::function영 (대해 int()) 다음 결과 비교해보다 큰 연산자를 사용 bool대하여 f. 그러나 잘 아시다시피, boost::function 실생활 에서 템플릿은 컴파일러이므로 (14.2 / 3)을 알고 있습니다.

이름 조회 (3.4)에서 이름이 템플릿 이름임을 확인한 후이 이름 뒤에 <가 오는 경우 <는 항상 템플릿 인수 목록의 시작으로 간주되고 이름 뒤에는 연산자보다.

이제와 같은 문제로 돌아 왔습니다 typename. 코드를 구문 분석 할 때 이름이 템플릿인지 여부를 아직 알 수 없으면 어떻게합니까? 에 template지정된대로 템플릿 이름 바로 앞에 삽입해야합니다 14.2/4. 이것은 다음과 같습니다

t::template f<int>(); // call a function template

템플릿 이름은 a 이후 ::뿐만 아니라 a ->또는 .클래스 멤버 액세스 후에도 발생할 수 있습니다. 키워드도 여기에 삽입해야합니다.

this->template f<int>(); // call a function template

의존성

선반에 두꺼운 표준 책이 있고 내가 정확히 무슨 말을하는지 알고 싶어하는 사람들을 위해, 나는 이것이 표준에서 어떻게 지정되는지에 대해 조금 이야기 할 것입니다.

템플릿 선언에서 일부 구문은 템플릿을 인스턴스화하는 데 사용하는 템플릿 인수에 따라 다른 의미를 갖습니다. 표현식에는 유형이나 값이 다르거 나 변수에 유형이 다르거 나 함수 호출에 따라 서로 다른 함수가 호출 될 수 있습니다. 이러한 구성은 일반적으로 템플릿 매개 변수 에 의존 한다고합니다 .

표준은 구조가 종속적인지 여부에 따라 정확하게 규칙을 정의합니다. 논리적으로 다른 그룹으로 분리합니다. 하나는 유형을, 다른 하나는 표현식을 포착합니다. 표현은 그 가치 및 / 또는 유형에 따라 달라질 수 있습니다. 따라서 일반적인 예제가 추가되었습니다.

  • 종속 유형 (예 : 유형 템플리트 매개 변수 T)
  • 값 종속 표현식 (예 : 유형이 아닌 템플리트 매개 변수 N)
  • 유형 종속 표현식 (예 : 유형 템플리트 매개 변수로 캐스트 (T)0)

규칙의 대부분은 직관적이고 반복적으로 구축되어 있습니다 : 예를 들어, 같은 구성 유형은 T[N]경우에 따라 유형 N값에 의존하는 표현 또는 T종속 유형입니다. 이에 대한 자세한 내용은 (14.6.2/1종속 유형, (14.6.2.2)유형 종속 표현식 및 (14.6.2.3)값 종속 표현식에 대해 섹션에서 읽을 수 있습니다 .

종속 이름

표준은 정확히 종속 이름 이 무엇인지에 대해 조금 불분명 합니다. 간단한 읽기 (최소한의 놀라움의 원리)에서 종속 이름으로 정의 된 모든 것은 아래 함수 이름의 특수한 경우입니다. 그러나 T::x인스턴스화 컨텍스트에서 명확하게 찾아 볼 필요가 있기 때문에 종속 이름이어야합니다 (다행히도 C ++ 14 중반부터위원회는이 혼란스러운 정의를 수정하는 방법을 조사하기 시작했습니다).

이 문제를 피하기 위해 표준 텍스트를 간단히 해석했습니다. 종속 유형 또는 표현식을 나타내는 모든 구성 중에서 이들 중 일부는 이름을 나타냅니다. 따라서 이러한 이름은 "종속적 인 이름"입니다. 이름은 다른 형태를 취할 수 있습니다-표준은 말합니다 :

이름은 엔터티 또는 레이블 (6.6.4, 6.1)

다음이가있는 동안 식별자, 그냥 문자 / 숫자의 일반 순서입니다 operator +operator type양식. 마지막 형태는 template-name <argument list>입니다. 이러한 이름은 모두 이름이며 표준에서 일반적으로 사용하면 이름을 찾아야하는 네임 스페이스 또는 클래스를 말하는 한정자도 이름에 포함될 수 있습니다.

값 종속 표현식 1 + N은 이름이 아니라 N입니다. 이름 인 모든 종속 구성의 서브 세트를 종속 이름 이라고 합니다. 그러나 함수 이름은 템플릿의 다른 인스턴스화에서 다른 의미를 가질 수 있지만 불행히도이 일반적인 규칙에 따라 잡히지 않습니다.

종속 함수 이름

이 기사의 주요 관심사는 아니지만 여전히 언급 할 가치가 있습니다. 함수 이름은 별도로 처리되는 예외입니다. 식별자 함수 이름은 그 자체가 아니라 호출에 사용되는 형식 종속 인수 표현식에 따라 다릅니다. 이 예 f((T)0)에서는 f종속 이름입니다. 표준에서 이것은에 지정되어 (14.6.2/1)있습니다.

추가 메모 및 예

충분한 경우에 typename와 둘 다 필요합니다 template. 코드는 다음과 같아야합니다

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

키워드 template가 항상 이름의 마지막 부분에 나타나는 것은 아닙니다. 다음 예제와 같이 범위로 사용되는 클래스 이름 앞에 중간에 나타날 수 있습니다.

typename t::template iterator<int>::value_type v;

경우에 따라 아래에 설명 된대로 키워드가 금지됩니다.

  • 종속 기본 클래스의 이름으로 작성할 수 없습니다 typename. 주어진 이름은 클래스 타입 이름이라고 가정합니다. 이것은 기본 클래스 목록과 생성자 이니셜 라이저 목록의 이름 모두에 해당됩니다.

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
  • 사용 선언 template에서 마지막 이후에는 사용할 수 없으며 ::C ++위원회 해결책을 찾지 말라고 말했습니다 .

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };

22
이 답변은 이전에 삭제 한 이전 FAQ 항목에서 복사 한 것입니다. 답변에 대한 목적으로 새로운 "의사 질문"을 작성하는 대신 기존의 유사한 질문을 더 잘 사용해야한다는 것을 알았습니다. 감사합니다 @Prasoon , 마지막 부분 (typename / template이 금지 된 경우)의 아이디어를 답변으로 편집했습니다.
Johannes Schaub-litb

1
이 구문을 언제 사용해야합니까? 이-템플릿 f <int> (); 이 오류 '템플릿'(동음이 분명하지 않음)은 템플릿 내에서만 허용되지만 템플릿 키워드가 없으면 정상적으로 작동합니다.
balki의

1
나는 오늘 비슷한 질문을했는데, 그것은 곧 중복으로 표시되었습니다 : stackoverflow.com/questions/27923722/… . 나는 새로운 질문을 만드는 대신이 질문을 되살 리라는 지시를 받았다. 나는 그들이 중복되는 것에 동의하지 않는다고 말해야하지만, 나는 누구입니까? 그렇다면 typename구문이 유형 이름 이외의 다른 해석을 허용하지 않는 경우에도 적용되는 이유가 있습니까?
JorenHeit

1
@Pablo 당신은 아무것도 누락되지 않았습니다. 그러나 완전한 행이 더 이상 모호하지 않더라도 명확성을 작성해야합니다.
Johannes Schaub-litb

1
@Pablo의 목적은 언어와 컴파일러를 더 단순하게 유지하는 것입니다. 더 많은 상황에서 자동으로 상황을 파악할 수 있도록 키워드가 덜 필요하다는 제안이 있습니다. 귀하의 예에서 토큰 모호하며 이중 후 ">"를 본 후에 만 ​​템플릿 꺾쇠 괄호로 명확하게 표시 할 수 있습니다. 자세한 내용은 C ++ 컴파일러 파서를 구현 한 경험이 없기 때문에 물어 보는 사람이 잘못입니다.
Johannes Schaub-litb

135

C ++ 11

문제

C ++ 03의 규칙은 필요할 때 typenametemplate상당히 합리적이지만 그 공식화에는 한 가지 성가신 단점이 있습니다.

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

볼 수 있듯이, 우리는 동음 키워드를 필요로하는 경우에도 컴파일러 수 자체 밖으로 완벽하게 그림 A::result_type만 할 수있다 int(따라서 일종), 그리고 this->g유일한 멤버 템플릿이 될 수 g이상 (선언 된 경우에도 A그 것, 명시 적으로 어딘가를 전문 해당 템플릿 내의 코드에는 영향을 미치지 않으므로 그 의미는 나중에 A!)의 전문화에 영향을받을 수 없습니다 .

현재 인스턴스화

상황을 개선하기 위해 C ++ 11에서는 유형이 엔 클로징 템플리트를 참조 할 때 언어가 추적합니다. 알고하려면 유형은 자신의 이름이 이름의 특정 형태 (위의를 사용하여 형성되어 있어야합니다 A, A<T>, ::A<T>). 이러한 이름으로 참조되는 유형은 현재 인스턴스화로 알려져 있습니다 . 이름이 형성되는 형태가 부재 / 중첩 클래스의 경우 현재의 모든 인스턴스이다 (그리고, 여러 가지 종류가있을 수 A::NestedClassA양 전류 인스턴스화이다).

이 개념을 바탕으로, 언어는 말한다 CurrentInstantiation::Foo, Foo그리고 CurrentInstantiationTyped->Foo(예 : A *a = this; a->Foo모두) 현재 인스턴스의 멤버 경우 그들은 단지 수행하여 현재 인스턴스 또는 비 의존 기본 클래스의 하나 인 클래스의 멤버 (로 발견 이름 조회 즉시).

키워드 typename및이 template규정은 현재 인스턴스의 구성원 인 경우 지금은 더 이상 필요하지 않습니다. 여기서 기억해야 할 핵심 사항 A<T>여전히 유형에 따른 이름이라는 것입니다 (결국 T유형에 따라 달라짐). 그러나 A<T>::result_type유형으로 알려져 있습니다. 컴파일러는 이러한 유형의 종속 유형을 "마 법적으로"조사하여이를 파악합니다.

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

인상적이지만 더 잘할 수 있습니까? 언어는 더 나아가 및 요구 구현이 다시 보이는 것을 D::result_type인스턴스화 할 때 D::f(이 정의시에 이미 그 의미를 찾을 경우에도). 이제 조회 결과가 다르거 나 모호한 결과가 발생하면 프로그램이 잘못 구성되고 진단이 제공되어야합니다. 우리가 정의 된 경우 발생하는 상상 C이 같은

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

인스턴스화 할 때 오류를 잡으려면 컴파일러가 필요합니다 D<int>::f. 당신은 두 세계의 최고 얻을 그래서 : 당신이 의존 기본 클래스에 문제가 얻을 수 있다면 당신을 보호 조회 "지연", 또한 "즉시"조회가 해방 당신은에서 typenametemplate.

알 수없는 전문화

코드 D에서 이름 typename D::questionable_type은 현재 인스턴스화의 멤버가 아닙니다. 대신이 언어는이를 알 수없는 전문화 영역구성원으로 표시합니다 . 특히, 이것은 당신이하고있는 경우 항상 DependentTypeName::FooDependentTypedName->Foo하고 중 종속 유형이 없는 현재의 인스턴스가 (이 경우 컴파일러는 포기하고 우리가 나중에 무엇을 볼 것이다 "라고 Foo이다) 또는 그것 입니다 현재 인스턴스와 이름이 해당 비 종속 기본 클래스에서 발견되지 않았으며 종속 기본 클래스도 있습니다.

h위에서 정의한 A클래스 템플릿 내에 멤버 함수가 있으면 어떻게 될지 상상해보십시오.

void h() {
  typename A<T>::questionable_type x;
}

C ++ 03에서는 인스턴스화하는 유효한 방법이 없기 때문에 언어 가이 오류를 잡을 수있었습니다 A<T>::h(어떤 인수에주십시오 T). C ++ 11에서 언어는 이제 컴파일러가이 규칙을 구현해야하는 더 많은 이유를 제공하기 위해 추가 검사를 수행합니다. 때문에 A더 의존 기본 클래스이 없으며, A어떤 멤버를 선언 questionable_type, 이름 A<T>::questionable_type입니다 현재 인스턴스의 멤버 알려지지 않은 전문화의 일원. 이 경우 해당 코드가 인스턴스화 시점에 올바르게 컴파일 될 수있는 방법이 없어야하므로 언어는 한정자가 현재 인스턴스화 인 이름을 알 수없는 전문화 영역의 멤버 나 현재 인스턴스화의 멤버가 아닌 이름을 금지합니다. 이 위반은 여전히 ​​진단 할 필요가 없습니다.)

예와 상식

이 답변 에 대해이 지식을 시험해 볼 수 있으며 위의 정의가 실제 예제에서 당신에게 적합한 지 확인할 수 있습니다 (그 답변에서 약간 덜 상세하게 반복됩니다).

C ++ 11 규칙은 다음과 같은 유효한 C ++ 03 코드를 잘못 작성합니다 (C ++위원회가 의도하지 않았지만 수정되지는 않음).

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

이 유효한 C ++ 03 코드를 결합 할 this->fA::f인스턴스화 시간에 모든 것이 괜찮습니다. 그러나 C ++ 11은 즉시 바인딩하고 바인딩 할 B::f때 조회가 여전히 일치하는지 확인하면서 이중 검사가 필요합니다. 인스턴스화 할 때 C<A>::g지배 규칙이 적용 조회 찾을 A::f대신.


fyi-이 답변은 여기에서 참조됩니다 : stackoverflow.com/questions/56411114/… 이 답변의 많은 코드는 다양한 컴파일러에서 컴파일되지 않습니다.
Adam Rackis 2016 년

@ AdamRackis는 C ++ 사양이 2013 년 이후로 변경되지 않았다고 가정하면 (이 답변을 작성한 날짜), 코드를 시도한 컴파일러는이 C ++ 11 + 기능을 아직 구현하지 않았습니다.
Johannes Schaub-litb

99

머리말

이 글은 litb 's post에 대한 읽기 쉬운 대안 입니다.

기본 목적은 동일합니다. "언제?" 그리고 왜?" typename하고 template적용해야합니다.


의 목적은 무엇 typenametemplate?

typenametemplate템플릿을 선언 할 때 이외의 상황에서 사용할 수 있습니다.

C ++ 에는 컴파일러가 이름을 다루는 방법을 명시 적으로 알려 주어야하는 특정 컨텍스트가 있으며 이러한 모든 컨텍스트에는 공통점이 있습니다. 그것들은 적어도 하나의 template-parameter 에 의존합니다 .

우리는 해석에 모호한 부분이있을 수있는 그러한 이름을 언급한다. " 종속 이름 ".

이 게시물은 dependent-names 과 두 키워드 의 관계에 대한 설명을 제공합니다 .


스 니펫은 1000 단어 이상을 말합니다

다음의 기능 템플릿 에서 무슨 일이 일어나고 있는지 , 자신, 친구 또는 고양이 에게 설명해보십시오 . ( A ) 로 표시된 명세서에서 무슨 일이 있습니까?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


생각하는 것처럼 쉽지 않을 수 있습니다.보다 구체적으로 ( A ) 를 평가 한 결과는 template-parameter로 전달 된 유형의 정의에 크게 좌우 됩니다 T.

서로 다른 T의미는 관련된 의미를 크게 바꿀 수 있습니다.

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


두 가지 시나리오 :

  • ( C ) 에서처럼 함수 템플릿을 X 유형으로 인스턴스화하면 x 라는 이름 의 포인터에 대한 선언이 있지만;

  • ( D ) 에서 와 같이 유형 Y로 템플릿을 인스턴스화하면 ( A )는 123의 곱에 이미 선언 된 변수 x를 곱한 값 을 계산하는 표현식으로 구성됩니다 .



근거

C ++ 표준은 적어도이 경우에 우리의 안전과 안녕에 관심을 갖습니다.

잠재적으로 불쾌한 놀라움을 앓고에서 구현을 방지하기 위해, 표준의 의무 우리는 일종의 a의 모호함에서 것을 의존 이름 에 의해 명시 적으로 의도 어디를 알리는 우리는 중 하나로 이름을 치료하고 싶습니다 형의 이름 , 또는 템플레이트 ID .

아무 것도 언급하지 않으면, dependent-name 은 변수 또는 함수로 간주됩니다.



상대방 이름을 처리하는 방법 ?

이 영화가 할리우드 영화인 경우, 부양적인 이름 은 신체 접촉을 통해 퍼지는 질병이되며, 즉시 호스트에게 영향을 미쳐 혼란스러워합니다. 아마도 잘못된 형식의 erhm .. 프로그램으로 이어질 수있는 혼란.

의존-이름 입니다 어떠한 직접적, 또는 간접적으로하는에 따라 이름 템플릿 매개 변수 .

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

위의 스 니펫 에는 네 개의 종속 이름이 있습니다.

  • E )
    • "유형" 의 인스턴스에 따라 달라집니다 SomeTrait<T>포함 T하고;
  • F )
    • "NestedTrait" A는, 템플릿 ID가 에 따라 달라집니다 SomeTrait<T>및;
    • ( F ) 끝에있는 "type"NestedTrait 에 따라 달라집니다 SomeTrait<T>.
  • G )
    • foo 의 유형이 의 인스턴스화에 의존하기 때문에 멤버 함수 템플릿 처럼 보이는 "data" 는 간접적으로 종속 이름 입니다 .SomeTrait<T>

컴파일러가 종속 이름 을 변수 / 함수로 해석하면 명령문 ( E ), ( F ) 또는 ( G ) 중 어느 것도 유효 하지 않습니다 (앞서 언급했듯이 명시 적으로 다르게 말하지 않으면 발생 함).

해결책

g_tmpl유효한 정의를 갖기 위해서는 컴파일러에게 유형 ( E ), 템플릿 ID유형 ( F ) 및 템플릿 ID ( G ) 가 필요하다고 명시 적으로 알려 주어야합니다 .

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

이름 이 유형을 나타낼 때마다 관련된 모든 이름유형 이름 또는 네임 스페이스 여야합니다.이를 염두에두고 정규화 된 이름typename 의 시작 부분에 적용한다는 것을 쉽게 알 수 있습니다 .

template그러나 다음과 같은 결론에 도달 할 방법이 없기 때문에 이와 관련하여 다릅니다. "오, 이것은 템플릿이고,이 다른 것 또한 템플릿이어야합니다" . 이것은 우리가 그렇게 취급하고 싶은 이름template 앞에 직접 적용한다는 것을 의미합니다 .



CAN I JUST 스틱 키워드 어떠한 이름의 프런트?

" 캔 난 그냥 스틱 typenametemplate... 어떤 이름 앞에 나는 그들이 나타나는 상황에 대해 걱정 싶지 않아? "-Some C++ Developer

표준의 규칙에 따르면 규정 된 이름 ( K )을 처리하는 한 키워드를 적용 할 수 있지만 규정 되지 않은 경우 응용 프로그램의 형식이 잘못 ( L )됩니다.

namespace N {
  template<class T>
  struct X { };
}

         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

참고 : 필요하지 않은 상황에서 적용 typename하거나 적용 template하는 것은 좋은 습관으로 간주되지 않습니다. 무언가를 할 수 있다고해서 꼭해야한다는 의미는 아닙니다.


또한 컨텍스트가 typename하고 template있다 명시 적으로 허용되지는 :

  • 클래스가 상속 할 기본을 지정할 때

    파생 클래스의 base-specifier-list에 작성된 모든 이름 은 이미 type-name 으로 취급되며 명시 적으로 typename잘못 지정 되고 중복됩니다.

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };


  • 경우 템플릿 ID는 하나는 클래스의 파생에 언급되고 사용 지시어

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };

20
typedef typename Tail::inUnion<U> dummy;

그러나 inUnion의 구현이 올바른지 확실하지 않습니다. 올바르게 이해하면이 클래스를 인스턴스화하지 않아야하므로 "실패"탭이 절대로 실패하지 않습니다. 어쩌면 유형이 단순한 부울 값과 결합인지 아닌지를 나타내는 것이 좋습니다.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS : Boost :: Variant 살펴보기

PS2 : 특히 Andrei Alexandrescu의 책 : Modern C ++ Design 에서 typelists를 살펴보십시오 .


예를 들어 U == int로 Union <float, bool> :: operator = (U)를 호출하려고하면 inUnion <U>가 인스턴스화됩니다. 개인 집합 (U, inUnion <U> * = 0)을 호출합니다.
MSalters

그리고 result = true / false의 작업은 현재 OSX 툴체인과 호환되지 않는 boost :: enable_if <>가 필요하다는 것입니다. 그러나 별도의 템플릿은 여전히 ​​좋은 생각입니다.
MSalters

Luc는 typedef Tail :: inUnion <U> 더미를 의미합니다. 선. 꼬리를 인스턴스화합니다. inUnion <U>는 아닙니다. 전체 정의가 필요할 때 인스턴스화됩니다. 예를 들어 sizeof를 가져 오거나 멤버에 액세스하는 경우 (:: foo 사용) 이런 일이 발생합니다. @MSalters 어쨌든, 당신은 또 다른 문제가 있습니다 :
Johannes Schaub-litb

size_t는 부호없는 정수 유형이므로 -sizeof (U)는 음수가 아닙니다. 당신은 매우 높은 숫자를 얻을 것입니다. sizeof (U)> = 1 하시겠습니까? -1 : 1 또는 이와 유사한 :)
Johannes Schaub-litb

나는 그것을 정의하지 않고 그대로 선언 할 것입니다 : template <typename U> struct inUnion; 확실히 인스턴스화 할 수 없습니다. sizeof (U)가 항상> = 1이고 알기 때문에 – 컴파일러가 sizeof (U) 를 갖는 경우 컴파일러가 인스턴스화 하지 않아도 오류를 줄 수 있다고 생각합니다 .
Johannes Schaub-litb

20

이 답변은 제목이 지정된 질문에 (일부) 대답하기에 다소 짧고 달콤한 답변입니다. 왜 거기에 배치해야하는지 설명하는 자세한 답변을 원하면 여기 로 이동 하십시오 .


typename키워드 를 삽입하는 일반적인 규칙 은 주로 템플릿 매개 변수를 사용하고 중첩 typedef또는 별칭을 사용 하려는 경우입니다.

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

이는 메타 함수 또는 일반 템플릿 매개 변수를 사용하는 항목에도 적용됩니다. 그러나 제공된 템플리트 매개 변수가 명시 적 유형 인 경우 다음과 같이 지정할 필요가 없습니다 typename.

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

template한정자 를 추가하는 일반적인 규칙은 일반적으로 템플릿으로 작성된 구조체 / 클래스의 템플릿 멤버 함수 (정적 또는 기타)를 포함한다는 점을 제외하면 대부분 비슷합니다.

이 구조체와 기능이 주어지면 :

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

t.get<int>()함수 내부에서 액세스하려고 하면 오류가 발생합니다.

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

따라서 이러한 맥락에서 template사전 에 키워드 가 필요하며 다음과 같이 호출하십시오.

t.template get<int>()

그렇게하면 컴파일러는 이것을 대신 올바르게 구문 분석합니다 t.get < int.


2

필자 는 주제에 대해 읽은 가장 간결한 설명이므로 cplusplus.com의 유사한 질문에 대해 JLBorges의 훌륭한 답변 을 제시하고 있습니다.

우리가 작성하는 템플릿에는 종속 이름과 비 종속 이름의 두 가지 이름이 있습니다. 종속 이름은 템플릿 매개 변수에 의존하는 이름입니다. 비 의존적 이름은 템플릿 매개 변수가 무엇이든 관계없이 동일한 의미를 갖습니다.

예를 들면 다음과 같습니다.

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)

    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

종속 이름이 말하는 것은 템플릿의 각 인스턴스화마다 다를 수 있습니다. 결과적으로 C ++ 템플릿에는 "2 단계 이름 조회"가 적용됩니다. 템플릿이 처음 구문 분석 될 때 (인스턴스화가 발생하기 전에) 컴파일러는 비 종속적 이름을 찾습니다. 템플릿의 특정 인스턴스화가 발생하면 템플릿 매개 변수를 알고 컴파일러가 종속 이름을 찾습니다.

첫 번째 단계에서 구문 분석기는 종속 이름이 유형의 이름인지 또는 비 유형의 이름인지 알아야합니다. 기본적으로 종속 이름은 유형이 아닌 이름으로 간주됩니다. 종속 이름 앞의 typename 키워드는 유형 이름임을 지정합니다.


요약

유형을 참조하고 템플리트 매개 변수에 의존하는 규정 된 이름이있는 경우 템플리트 선언 및 정의에서만 키워드 typename을 사용하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.