Chính thức, typename để làm gì?


131

Thỉnh thoảng tôi thấy một số thông báo lỗi thực sự không thể giải mã được phát ra gcckhi sử dụng các mẫu ... Cụ thể, tôi đã gặp vấn đề khi các khai báo dường như đúng gây ra các lỗi biên dịch rất kỳ lạ đã biến mất một cách kỳ diệu bằng cách typenamebắt đầu từ khóa vào đầu khai báo ... (Ví dụ, chỉ tuần trước, tôi đã tuyên bố hai trình vòng lặp là thành viên của một lớp templated khác và tôi phải làm điều này) ...

Câu chuyện trên là typenamegì?


Câu trả lời:


207

Sau đây là trích dẫn từ cuốn sách Josuttis:

Từ khóa typenameđược giới thiệu để xác định rằng định danh theo sau là một loại. Hãy xem xét ví dụ sau:

template <class T>
Class MyClass
{
  typename T::SubType * ptr;
  ...
};

Ở đây, typenameđược sử dụng để làm rõ đó SubTypelà một loại class T. Vì vậy, ptrlà một con trỏ đến loại T::SubType. Nếu không typename, SubType sẽ được coi là một thành viên tĩnh. Như vậy

T::SubType * ptr

sẽ là một phép nhân của giá trị SubTypecủa loại Tvới ptr.


2
Cuốn sách tuyệt vời Đọc qua một lần rồi giữ nó làm tài liệu tham khảo nếu bạn thích.
deft_code

1
Người đọc sắc sảo sẽ nhận ra rằng một biểu thức nhân không được phép bởi ngữ pháp cho một tuyên bố thành viên. Như vậy, C ++ 20 phân phối với nhu cầu này typename(mặc dù không phải tất cả trong số họ!).
Davis Herring

Không thuyết phục tôi. Khi mẫu đang được khởi tạo, nó được xác định rất rõ T :: Subtype
kovarex

36

Bài viết BLog của Stan Lippman gợi ý: -

Stroustrup đã sử dụng lại từ khóa lớp hiện tại để chỉ định một tham số loại thay vì giới thiệu một từ khóa mới, tất nhiên có thể phá vỡ các chương trình hiện có. Không phải là một từ khóa mới không được xem xét - chỉ là nó không được coi là cần thiết do sự gián đoạn tiềm năng của nó. Và cho đến tiêu chuẩn ISO-C ++, đây là cách duy nhất để khai báo một tham số loại.

Vì vậy, về cơ bản, Stroustrup đã sử dụng lại từ khóa lớp mà không giới thiệu một từ khóa mới được thay đổi sau đó trong tiêu chuẩn vì những lý do sau

Như ví dụ đã cho

template <class T>
class Demonstration {
public:
void method() {
    T::A *aObj; // oops …
     // …
};

ngữ pháp ngôn ngữ hiểu sai T::A *aObj;như một biểu thức số học, vì vậy một từ khóa mới được giới thiệu được gọi làtypename

typename T::A* a6;

nó ra lệnh cho trình biên dịch coi câu lệnh tiếp theo là một khai báo.

Vì từ khóa đã có trong bảng lương, heck, tại sao không khắc phục sự nhầm lẫn gây ra bởi quyết định ban đầu để sử dụng lại từ khóa lớp.

Đó là lý do tại sao chúng ta có cả hai

Bạn có thể xem bài đăng này , nó chắc chắn sẽ giúp bạn, tôi chỉ trích xuất từ ​​nó nhiều nhất có thể


Có, nhưng tại sao một từ khóa mới lại typenamecần thiết, nếu bạn có thể sử dụng từ khóa hiện tại classcho cùng một mục đích?
Jesper

5
@Jesper: Tôi nghĩ câu trả lời của Xenus khó hiểu ở đây. typenametrở nên cần thiết để khắc phục vấn đề phân tích cú pháp như được mô tả trong câu trả lời của Naveen bằng cách trích dẫn Josuttis. (Tôi không nghĩ việc chèn classtại địa điểm này sẽ có hiệu quả.) Chỉ sau khi từ khóa mới được chấp nhận cho trường hợp này, nó cũng được cho phép trong khai báo đối số mẫu ( hoặc đó là định nghĩa? ), Bởi vì classluôn có phần nào đó gây hiểu lầm.
sbi

13

Xem xét mã

template<class T> somefunction( T * arg )
{
    T::sometype x; // broken
    .
    .

Thật không may, trình biên dịch không bắt buộc phải là nhà ngoại cảm, và không biết liệu T :: somype sẽ kết thúc bằng cách gọi tên loại hay thành viên tĩnh của T. Vì vậy, người ta sử dụng typenameđể nói với nó:

template<class T> somefunction( T * arg )
{
    typename T::sometype x; // works!
    .
    .

6

Trong một số trường hợp khi bạn đề cập đến một thành viên của cái gọi là loại phụ thuộc (có nghĩa là "phụ thuộc vào tham số mẫu"), trình biên dịch không thể luôn luôn suy diễn một cách rõ ràng ý nghĩa ngữ nghĩa của cấu trúc kết quả, bởi vì nó không biết đó là loại tên gì (tức là cho dù đó là tên của một loại, tên của một thành viên dữ liệu hoặc tên của một thứ khác). Trong trường hợp như vậy, bạn phải phân biệt tình huống bằng cách thông báo rõ ràng cho trình biên dịch rằng tên thuộc về một kiểu chữ được xác định là thành viên của loại phụ thuộc đó.

Ví dụ

template <class T> struct S {
  typename T::type i;
};

Trong ví dụ này, từ khóa typenamecần thiết cho mã để biên dịch.

Điều tương tự cũng xảy ra khi bạn muốn tham chiếu đến một thành viên mẫu thuộc loại phụ thuộc, tức là một tên chỉ định mẫu. Bạn cũng phải giúp trình biên dịch bằng cách sử dụng từ khóa template, mặc dù nó được đặt khác nhau

template <class T> struct S {
  T::template ptr<int> p;
};

Trong một số trường hợp có thể cần phải sử dụng cả hai

template <class T> struct S {
  typename T::template ptr<int>::type i;
};

(nếu tôi có cú pháp chính xác).

Tất nhiên, một vai trò khác của từ khóa typenamesẽ được sử dụng trong khai báo tham số mẫu.


Xem thêm Mô tả về từ khóa tên kiểu C ++ để biết thêm thông tin (nền).
Atafar

5

Bí mật nằm ở chỗ một mẫu có thể được chuyên dùng cho một số loại. Điều này có nghĩa là nó cũng có thể định nghĩa giao diện hoàn toàn khác nhau cho một số loại. Ví dụ bạn có thể viết:

template<typename T>
struct test {
    typedef T* ptr;
};

template<>         // complete specialization 
struct test<int> { // for the case T is int
    T* ptr;
};

Người ta có thể hỏi tại sao điều này hữu ích và thực sự: Điều đó thực sự trông vô dụng. Nhưng hãy nhớ rằng ví dụ std::vector<bool>các referenceloại trông hoàn toàn khác so với khác Ts. Phải thừa nhận rằng nó không thay đổi loại referencetừ loại này sang loại khác nhưng tuy nhiên nó có thể xảy ra.

Bây giờ điều gì xảy ra nếu bạn viết các mẫu của riêng bạn bằng cách sử dụng testmẫu này . Một cái gì đó như thế này

template<typename T>
void print(T& x) {
    test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

nó có vẻ ổn đối với bạn bởi vì bạn mong đợi đó test<T>::ptrlà một loại. Nhưng trình biên dịch không biết và trong thực tế, anh ta thậm chí còn được tiêu chuẩn khuyên rằng điều ngược lại, test<T>::ptrkhông phải là một loại. Để nói với trình biên dịch những gì bạn mong đợi bạn phải thêm một typenametrước đó. Mẫu chính xác trông như thế này

template<typename T>
void print(T& x) {
    typename test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

Dòng dưới cùng: Bạn phải thêm typenametrước bất cứ khi nào bạn sử dụng loại mẫu lồng nhau trong các mẫu của bạn. (Tất nhiên chỉ khi một tham số mẫu của mẫu của bạn được sử dụng cho mẫu bên trong đó.)


5

Hai công dụng:

  1. Là một templatetừ khóa đối số (thay vì class)
  2. Một typenametừ khóa cho trình biên dịch biết rằng một định danh là một loại (chứ không phải là một biến thành viên tĩnh)
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}

4

Tôi nghĩ rằng tất cả các câu trả lời đã đề cập rằng typenametừ khóa, được sử dụng trong hai trường hợp khác nhau:

a) Khi khai báo một tham số kiểu mẫu. ví dụ

template<class T> class MyClass{};        // these two cases are
template<typename T> class MyNewClass{};  // exactly the same.

Không có sự khác biệt giữa chúng và chúng hoàn toàn giống nhau.

b) Trước khi sử dụng tên loại phụ thuộc lồng nhau cho mẫu.

template<class T>
void foo(const T & param)
{
   typename T::NestedType * value; // we should use typename here
}

Mà không sử dụng typenamedẫn đến lỗi phân tích / biên dịch.

Điều tôi muốn thêm vào trường hợp thứ hai, như được đề cập trong cuốn sách Scot Meyers có hiệu quả C ++ , là có một ngoại lệ sử dụng typenametrước tên loại phụ thuộc lồng nhau . Ngoại lệ là nếu bạn sử dụng tên loại phụ thuộc lồng nhau làm lớp cơ sở hoặc trong danh sách khởi tạo thành viên , bạn không nên sử dụng typenameở đó:

template<class T>
class D : public B<T>::NestedType               // No need for typename here
{
public:
   D(std::string str) : B<T>::NestedType(str)   // No need for typename here
   {
      typename B<T>::AnotherNestedType * x;     // typename is needed here
   }
}

Lưu ý: Không cần sử dụng typenamecho trường hợp thứ hai (tức là trước tên loại phụ thuộc lồng nhau) không cần thiết kể từ C ++ 20.


2
#include <iostream>

class A {
public:
    typedef int my_t;
};

template <class T>
class B {
public:
    // T::my_t *ptr; // It will produce compilation error
    typename T::my_t *ptr; // It will output 5
};

int main() {
    B<A> b;
    int my_int = 5;
    b.ptr = &my_int;
    std::cout << *b.ptr;
    std::cin.ignore();
    return 0;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.