2008-08-14 18 views
32

Các mã sau đây không biên dịch với gcc, nhưng không với Visual Studio:vấn đề GCC: sử dụng một thành viên của một lớp cơ sở mà phụ thuộc vào một mẫu đối số

template <typename T> class A { 
public: 
    T foo; 
}; 

template <typename T> class B: public A <T> { 
public: 
    void bar() { cout << foo << endl; } 
}; 

tôi nhận được lỗi:

test.cpp: In member function ‘void B::bar()’:

test.cpp:11: error: ‘foo’ was not declared in this scope

Nhưng nó nên được! Nếu tôi thay đổi bar để

void bar() { cout << this->foo << endl; } 

sau đó nó không biên dịch, nhưng tôi không nghĩ rằng tôi phải làm điều này. Có điều gì đó trong thông số kỹ thuật chính thức của C++ mà GCC đang theo dõi ở đây không, hay nó chỉ là một sự gian lận?

+0

Xem [Trong một lớp dẫn xuất có templated, tại sao tôi cần phải đủ điều kiện cho tên thành viên lớp cơ sở với "this->" bên trong một hàm thành viên?] (Http://stackoverflow.com/questions/7908248/in-a- templated-derived-class-why-do-i-need-to-đủ điều kiện-base-class-member-names-w) – curiousguy

Trả lời

10

này thay đổi trong gcc-3.4. Trình phân tích cú pháp C++ trở nên nghiêm ngặt hơn nhiều trong bản phát hành đó - theo thông số kỹ thuật nhưng vẫn gây phiền toái cho những người có cơ sở mã đa nền tảng cũ hoặc đa nền tảng.

18

Wow. C++ không bao giờ ngừng làm tôi ngạc nhiên với sự kỳ quặc của nó.

In a template definition, unqualified names will no longer find members of a dependent base (as specified by [temp.dep]/3 in the C++ standard). For example,

template <typename T> struct B { 
    int m; 
    int n; 
    int f(); 
    int g(); 
}; 
int n; 
int g(); 
template <typename T> struct C : B<T> { 
    void h() 
    { 
    m = 0; // error 
    f(); // error 
    n = 0; // ::n is modified 
    g(); // ::g is called 
    } 
}; 

You must make the names dependent, e.g. by prefixing them with this->. Here is the corrected definition of C::h,

template <typename T> void C<T>::h() 
{ 
    this->m = 0; 
    this->f(); 
    this->n = 0 
    this->g(); 
} 

As an alternative solution (unfortunately not backwards compatible with GCC 3.3), you may use using declarations instead of this->:

template <typename T> struct C : B<T> { 
    using B<T>::m; 
    using B<T>::f; 
    using B<T>::n; 
    using B<T>::g; 
    void h() 
    { 
    m = 0; 
    f(); 
    n = 0; 
    g(); 
    } 
}; 

Đó chỉ là tất cả các loại điên. Cảm ơn, David.

Đây là "temp.dep/3" phần của tiêu chuẩn [ISO/IEC 14882: 2003] rằng họ đang đề cập đến:

In the definition of a class template or a member of a class template, if a base class of the class template 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. [Example:

typedef double A; 
template<class T> class B { 
    typedef int A; 
}; 
template<class T> struct X : B<T> { 
    A a; // a has typedouble 
}; 

The type name A in the definition of X<T> binds to the typedef name defined in the global namespace scope, not to the typedef name defined in the base class B<T> . ] [Example:

struct A { 
    struct B { /* ... */ }; 
    int a; 
    int Y; 
}; 
int a; 
template<class T> struct Y : T { 
    struct B { /* ... */ }; 
    B b; //The B defined in Y 
    void f(int i) { a = i; } // ::a 
    Y* p; // Y<T> 
}; 
Y<A> ya; 

The members A::B , A::a , and A::Y of the template argument A do not affect the binding of names in Y<A> . ]

33

David Joyner có lịch sử, đây là lý do.

Vấn đề khi biên dịch B<T> là lớp cơ sở của nó A<T> không được biết từ trình biên dịch, là một lớp mẫu, vì vậy không có cách nào để trình biên dịch biết bất kỳ thành viên nào từ lớp cơ sở.

Đầu phiên bản đã làm một số suy luận bằng cách thực sự phân tích các cơ sở mẫu lớp, nhưng ISO C++ nói rằng suy luận này có thể dẫn đến xung đột nơi không nên có.

Các giải pháp để tham khảo một thành viên lớp cơ sở trong một mẫu là sử dụng this (như bạn đã làm) hoặc cụ thể đặt tên cho lớp cơ sở:

template <typename T> class A { 
public: 
    T foo; 
}; 

template <typename T> class B: public A <T> { 
public: 
    void bar() { cout << A<T>::foo << endl; } 
}; 

biết thêm thông tin trong gcc manual.

+8

Một mặt, loại đó có ý nghĩa. Nhưng mặt khác, nó cảm thấy thực sự què. Trình biên dịch không * cần * để biết những gì 'foo' đề cập đến cho đến khi khuôn mẫu được khởi tạo, tại thời điểm đó, nó sẽ có thể nhận ra thành viên' foo' trong 'A'. C++ có quá nhiều trường hợp góc kỳ lạ. –

+0

có, điều này hoàn toàn không thể chấp nhận được ... không thể biết trước khi khởi tạo? sau đó chờ đợi cho instantiation như bình thường trong các mẫu .. đó là tinh thần của nó, không? thật là một mớ hỗn độn ... –

8

Các chính lý do C++ không thể giả định bất cứ điều gì ở đây là mẫu cơ sở có thể được chuyên cho một loại sau. Tiếp tục ví dụ ban đầu:

template<> 
class A<int> {}; 

B<int> x; 
x.bar();//this will fail because there is no member foo in A<int> 
3

VC không thực hiện tra cứu hai pha, trong khi GCC thực hiện.Vì vậy, GCC phân tích mẫu trước khi chúng được khởi tạo và do đó tìm thấy nhiều lỗi hơn VC. Trong ví dụ của bạn, foo là một tên phụ thuộc, vì nó phụ thuộc vào 'T'. Trừ khi bạn nói cho trình biên dịch biết nó đến từ đâu, nó không thể kiểm tra tính hợp lệ của khuôn mẫu, trước khi bạn khởi tạo nó. Đó là lý do tại sao bạn phải nói cho trình biên dịch biết nó đến từ đâu.