2010-10-16 69 views
5

Bên trong C++ foo template function(), một cuộc gọi đến :: bar (TT *) cung cấp cho các lỗi sau đây theo gcc 4.4.3:Trong hàm mẫu C++, tại sao gọi hàm phụ thuộc cho lỗi "không khai báo"?

g++ -o hello.o -c -g hello.cpp 
hello.cpp: In function 'void foo(std::vector<TT*, std::allocator<TT*> >&)': 
hello.cpp:8: error: '::bar' has not been declared 

Dưới đây là mã vi phạm:

// hello.cpp 

#include <vector> 

template<typename TT> void foo(std::vector<TT*> &vec) 
{ 
    TT *tt; 
    ::bar(tt); 
    vec.push_back(tt); 
} 

class Blah 
{ 
}; 

void bar(Blah *&) 
{ 
} 

int main(int argc, char *argv[]) 
{ 
    std::vector<Blah*> vec; 
    foo(vec); 

    return 0; 
} 

C++ phân biệt giữa các biểu tượng phụ thuộc vào tham số mẫu (TT, ở đây) và các biểu tượng độc lập và có thể được đánh giá ngay lập tức.

Rõ ràng, trình biên dịch nghĩ rằng lời gọi :: bar (TT *) của tôi là độc lập và cố gắng giải quyết ngay lập tức. Cũng như rõ ràng, hàm đó gọi phụ thuộc vào TT vì hàm gọi có tham số kiểu TT *, do đó trình biên dịch phải chờ cho đến khi foo (vec) instantiation giải quyết :: bar (TT *).

Đây có phải là lỗi của gcc hoặc tôi thiếu điều gì đó tinh tế về mẫu C++ không?

EDIT: đây là ví dụ hơi phức tạp hơn với hai phiên bản :: bar() để làm rõ rằng thứ tự khai báo không phải là vấn đề với sự cố của tôi. Khi phân tích cú pháp mẫu, trình biên dịch có không cách biết nếu chính() xuống dưới đây sẽ khởi tạo chức năng mẫu với TT = Blah hoặc với TT = Argh. Do đó trình biên dịch không nên đưa ra lỗi cho đến khi dòng 35 dòng 28 sớm nhất (nếu có). Nhưng lỗi được đưa ra cho dòng 8 dòng 16.

EDIT # 2: cải thiện ví dụ này.

EDIT # 3: đã thêm sửa đổi cho ví dụ này để làm cho nó hoạt động như mong muốn. Thanh (tt) bây giờ đề cập chính xác đến thanh (Blah *). Lý do được đưa ra dưới đây. (Cảm ơn mọi người).

// hello.cpp 
#include <vector> 

class XX {}; 
void bar(XX*) {} 

class CC { 
public: 
    void bar(); 
    void bar(int *); 
    void bar(float *); 

    template<typename TT> static void foo(std::vector<TT*> &vec); 
}; 

template<typename TT> 
void CC::foo(std::vector<TT*> &vec) { 
    using ::bar; 
    TT *tt; 
    bar(tt); 
    vec.push_back(tt); 
} 

class Argh {}; 
void bar(Argh *&aa) { aa = new Argh; } 

class Blah {}; 
void bar(Blah *&bb) { bb = new Blah; } 

int main(int argc, char *argv[]) { 
    std::vector<Blah*> vec; 
    CC::foo(vec); 
    return 0; 
} 
+0

lỗi nói ":: thanh" không được khai báo không giống như "nguyên mẫu không khớp" ... có thể thanh thực sự không được khai báo? Đây là phỏng đoán –

+0

"Chỉ cần rõ ràng, hàm gọi đó phụ thuộc vào TT vì hàm gọi có tham số kiểu TT *, do đó trình biên dịch phải chờ cho đến khi diễn giải foo (vec) :: thanh (TT *) ". Sai rồi. Các tên ":: bar" và "bar" không phụ thuộc. Có một quy tắc đặc biệt để làm cho các tên không phụ thuộc khác phụ thuộc vào chúng khi chúng được sử dụng như là một chú thích, đó là dấu ngoặc kép @Chris văn bản và chỉ áp dụng cho các tên không đủ tiêu chuẩn. Những gì cần phải được thực hiện tại instantiation anyway là quá tải độ phân giải trên các tuyên bố được tìm thấy [s] với các đối số (phụ thuộc) nhất định. –

+0

Tôi đã đề cập điều này bên dưới, nhưng điều này dường như không đồng ý với bạn: Stroustrup TC++ PL Sp Ed, Phần C.13.8.1, Tên phụ thuộc: "Về cơ bản, tên của hàm được gọi là phụ thuộc nếu nó rõ ràng là phụ thuộc nhìn vào các đối số của nó hoặc tại các tham số chính thức của nó ". Theo tiêu chí đó, ':: bar' là" rõ ràng là phụ thuộc ". –

Trả lời

5

Nobody has yet pointed out any part of the current Standard that says I can't.

C++ 03 không làm cho tên ::bar phụ thuộc. Sự phụ thuộc xảy ra đối với các tên kiểu theo kiểu phụ thuộc, và đối với các tên không phải kiểu bằng các biểu thức phụ thuộc là kiểu phụ thuộc vào kiểu hoặc giá trị. Nếu một tên được tra cứu theo kiểu phụ thuộc, nó sẽ trở thành một biểu thức id phụ thuộc vào loại (bullet cuối cùng 14.6.2.2/3), và tra cứu của nó bị trì hoãn cho đến khi instantiation. Tên ::bar không có biểu thức phụ thuộc như vậy. Nếu bạn đã gọi bar(tt), một quy tắc đặc biệt của C++ 03 tại 14.2.6 nói

In an expression of the form:

postfix-expression (expression-listopt) 

where the postfix-expression is an identifier, the identifier denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.6.2.2).

Vì vậy, bạn cần phải loại bỏ các :: để làm cho nó một định danh và để làm cho nó phụ thuộc bởi quy tắc đặc biệt này .

The reason I can't remove the :: is that in my real code, the template function foo is a member function of class CC, and there exist a family of overloaded member functions CC::bar(...), meaning I need to qualify ::bar(TT*) to avoid defaulting to CC::bar(...). That's what :: exists for, I'm surprised if the Standard says I can't use :: here

Cách thích hợp để giải quyết là giới thiệu khai báo sử dụng vào phạm vi địa phương của hàm.

namespace dummies { void f(); } 
template<typename T> 
struct S { 
    void f(); 
    void g() { 
    using dummies::f; // without it, it won't work 
    f(T()); // with ::f, it won't work 
    } 
}; 

struct A { }; 
void f(A) { } // <- will find this 

int main() { 
    S<A> a; 
    a.g(); 
} 

ADL sẽ không làm gì nếu tra cứu thông thường tìm thấy chức năng của thành viên lớp học. Do đó, bạn giới thiệu một khai báo sử dụng, vì vậy tra cứu thông thường không tìm thấy một hàm thành viên của lớp và ADL có thể thúc đẩy các khai báo hiển thị khi khởi tạo.

But this seems to disagree with you: Stroustrup TC++PL Sp Ed, Section C.13.8.1, Dependent Names: "Basically, the name of a function called is dependent if it is obviously dependent by looking at its arguments or at its formal parameters"

Sách Stroustrup cũng được viết cho những người có thể chưa biết C++. Nó sẽ không cố gắng bao gồm tất cả các quy tắc với độ chính xác 100%, như là bình thường đối với những cuốn sách này. Các chi tiết đẫm máu được để lại cho người đọc tiêu chuẩn ISO.

Ngoài ra, các tham số chính thức của hàm không có gì liên quan đến việc cuộc gọi hàm có phụ thuộc hay không. Trong IS, chỉ các đối số thực sự xác định sự phụ thuộc của tên hàm. Điều này khác trong an old draft from 1996, có khái niệm ẩn ngụrõ ràng là phụ thuộc.Ngầm phụ thuộc được định nghĩa là

A name implicitly depends on a template-argument if it is a function name used in a function call and the function call would have a dif- ferent resolution or no resolution if a type, template, or enumerator mentioned in the template-argument were missing from the program.

[...]

[Example: some calls that depend on a template-argument type T are:

  1. The function called has a parameter that depends on T according to the type deduction rules (temp.deduct). For example, f(T), f(Array), and f(const T*).

  2. The type of the actual argument depends on T. For example, f(T(1)), f(t), f(g(t)), and f(&t) assuming that t has the type T.

Một ví dụ thực tế cũng được đưa ra

This ill-formed template instantiation uses a function that does not depend on a template-argument:

template<class T> class Z { 
public: 
     void f() const 
     { 
       g(1); // g() not found in Z's context. 
         // Look again at point of instantiation 
     } 
}; 

void g(int); 
void h(const Z<Horse>& x) 
{ 
     x.f(); // error: g(int) called by g(1) does not depend 
       // on template-argument ``Horse'' 
} 

The call x.f() gives rise to the specialization:

void Z<Horse>::f() { g(1); } 

The call g(1) would call g(int), but since that call does not depend on the template-argument Horse and because g(int) was not in scope at the point of the definition of the template, the call x.f() is ill- formed.

On the other hand:

void h(const Z<int>& y) 
{ 
     y.f(); // fine: g(int) called by g(1) depends 
       // on template-argument ``int'' 
} 

Here, the call y.f() gives rise to the specialization:

void Z<int>::f() { g(1); } 

The call g(1) calls g(int), and since that call depends on the tem- plate-argument int, the call y.f() is acceptable even though g(int) wasn't in scope at the point of the template definition. ]

Những điều này là trái với lịch sử, và ngay cả những dấu vết cuối cùng từ nó đang biến mất dần dần, mặc dù không chủ động điều khiển (n3126 cho Ví dụ loại bỏ "phụ thuộc rõ ràng" tại [temp.names]/p4 như là một tác dụng phụ của một thay đổi khác, bởi vì sự khác biệt giữa "phụ thuộc rõ ràng" và "phụ thuộc ngầm" chưa bao giờ tồn tại trong IS).

+0

Về '@Chris cho bạn thấy một phần của tiêu chuẩn đó': @Chris cho tôi thấy một từ ngữ từ tiêu chuẩn C++ 0x chưa được cạnh tranh mà @Alf nhanh chóng chỉ ra khác với tiêu chuẩn thực tế hiện tại - nơi mã của tôi là chính xác (theo C++ 98 §14.6.2). Ngoài ra, mã của tôi là trực quan chính xác. Chứng chỉ không ảnh hưởng đến sự phụ thuộc hoặc độc lập của biểu tượng mẫu vì bất kỳ lý do nào tôi ' Tại thời điểm diễn giải, trình biên dịch có rất nhiều thông tin để giải quyết chính xác :: bar (TT *) –

+0

Về ADL, bạn có nói rằng tôi nên thêm 'using :: bar' vào đầu của hàm mẫu Bởi vì tôi đã thử điều đó và nó không hoạt động trên gcc. –

+0

@Shawn một hàm 'bar' giả cần phải được khai báo trước mẫu của bạn, sau đó bạn đưa vào phạm vi cục bộ bằng cách khai báo sử dụng như vậy. : Cho chúng tôi thấy cách bạn kết luận rằng mã của bạn hợp lệ theo đoạn đó (theo sửa đổi câu hỏi của bạn).Nó không giúp đỡ nếu bạn chỉ cần nói với chúng tôi rằng mã của bạn là hợp lệ theo nó. Tôi đã cho bạn thấy rằng 14.6.2 chỉ áp dụng cho số nhận dạng và ":: bar" không phải là số nhận dạng. Tôi cũng cho bạn thấy rằng ADL sẽ không làm bất cứ điều gì cho các tên đủ điều kiện. Nếu bạn từ chối những gì tôi nói, tôi không thể giúp bạn. –

1

này nên làm việc, vấn đề của bạn là thứ tự tuyên bố:

// hello.cpp 

#include <vector> 


class Blah 
{ 
public: 

}; 

void bar(Blah *&) 
{ } 

template<typename TT> void foo(std::vector<TT*> &vec) 
{ 
    TT *tt; 
    ::bar(tt); 
    vec.push_back(tt); 
} 


int main(int argc, char *argv[]) 
{ 
    std::vector<Blah*> vec; 
    foo(vec); 

    return 0; 
} 
+0

Cũng đáng chú ý là vì anh ta không cung cấp cho bất kỳ đối số nào không phải là Blah * & in bar(), sau đó chỉ có các loại chuyển đổi thành Blah * và hoàn toàn có thể chấp nhận được là TT *, điều này khá nhiều so với Blah * . – Puppy

+0

Không có câu trả lời nào không chính xác. Có thể có nhiều phiên bản của thanh() lấy các loại con trỏ khác nhau cho một tham số của chúng. Một số trong số họ thậm chí còn chưa được phát minh. Đó là toàn bộ quan điểm của việc có một mẫu liên quan. –

+0

Nhưng không có nhiều phiên bản của thanh(). Chỉ có một. Tại sao có một hàm templated gọi một hàm không có khuôn mẫu với một đối số kiểu-phụ thuộc kiểu? Bạn chỉ giới hạn phạm vi của các đối số mẫu cho những đối số được chấp nhận bởi hàm không được tạo khuôn mẫu, bất chấp mục đích của chúng. – Puppy

0

Mã của bạn hoạt động tốt trên VS2005. vì vậy nó thực sự có vẻ giống như một lỗi trong gcc. (Tôi không biết những gì các thông số kỹ thuật nói về vấn đề này, có lẽ ai đó sẽ đi cùng và đăng nó.)

Đối với gcc, tôi cũng đã cố gắng xác định một tình trạng quá tải khác nhau của bar trước foo, do đó biểu tượng bar là ít nhất là định nghĩa:

void bar(float *) { 
} 

template<typename TT> void foo(std::vector<TT*> &vec) 
{ 
    TT *tt; 
    ::bar(tt); 
    vec.push_back(tt); 
} 

void bar(int *) { 
} 

int main(int argc, char *argv[]) 
{ 
    std::vector<int*> vec; 
    foo(vec); 

    return 0; 
} 

nhưng gcc là vẫn hoàn toàn mù đến int * quá tải:

error: cannot convert int* to float* for argument 1 to void bar(float*)

dường như gcc sẽ chỉ sử dụng những chức năng được định nghĩa trước chính khuôn mẫu đó.

Tuy nhiên, nếu bạn xóa thông số rõ ràng :: và chỉ làm cho số này là bar(tt), có vẻ như hoạt động tốt. Câu trả lời của Chris Dodd có vẻ thỏa đáng khi giải thích lý do tại sao điều này là như vậy.

+2

Tôi nghi ngờ rằng VC++ là sai ở đây. Nó chỉ đơn giản là không bận tâm với việc biên dịch hai giai đoạn của các khuôn mẫu và những thứ phụ thuộc/không phụ thuộc. Comeau đồng ý với GCC. - Để loại bỏ '::', tôi đoán điều này để lại tùy chọn mở mà một 'thanh' phù hợp sẽ được tìm thấy sau này bằng cách sử dụng tìm kiếm phụ thuộc vào đối số (có lẽ nó sẽ được tìm thấy trong không gian tên T thuộc về). – UncleBens

5

Từ phần 14.7.2 của ++ đặc tả C:

In an expression of the form:

postfix-expression (expression-listopt) 

where the postfix-expression is an unqualified-id but not a template-id , the unqualified-id denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.7.2.2).

từ ::b không phải là một id không đủ tiêu chuẩn, nó không phải là một tên phụ thuộc. Nếu bạn xóa ::, đó là tên không đủ tiêu chuẩn và do đó là tên phụ thuộc. Đối với một tên không phụ thuộc, tra cứu xảy ra tại thời điểm khai báo mẫu, không phải là sự khởi tạo, và tại thời điểm đó không có khai báo toàn cục nào của bar hiển thị, do đó bạn gặp lỗi.

+0

Trình biên dịch luôn luôn cần phải kiểm tra xem một tên đã được khai báo hay không nó phụ thuộc. Tra cứu tên và độ phân giải quá tải là hai thứ khác nhau. Cái đầu tiên xảy ra ở định nghĩa mẫu, cái sau tại thời điểm khởi tạo. – Potatoswatter

+0

-1 Không có từ ngữ nào trong tiêu chuẩn C++ 98. Đã có một sửa chữa cho C + + 03 nhưng tôi không biết những gì. Dự đoán tốt nhất của tôi là bạn đã xem một số bản nháp C++ 0x. Trong tiêu chuẩn C++ 98, nó là §14.6.2 trong đó nói, "Trong một biểu thức của biểu mẫu * biểu thức postfix (biểu thức danh sách) * trong đó * biểu thức postfix * là một * định danh *, mã định danh biểu thị * tên phụ thuộc * nếu và chỉ khi có bất kỳ các biểu thức trong * biểu thức danh sách * là một biểu thức phụ thuộc vào loại (14.6.2.2) ". Lemme kiểm tra ... Vâng, từ ngữ của bạn có mặt trong §14. *** 6 ***. 2 trong số *** C++ 0x *** draft. Uh, tôi đã cố gắng loại bỏ downvote –

+0

@Alf thật thú vị, bản nháp mới nhất n3126 không nói id-expression thay vì id không đủ tiêu chuẩn hoặc định danh. Điều này đã được thay đổi bởi [# 448] (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#448). Nhưng nó sẽ không làm bất cứ điều gì với mã của người hỏi: Mặc dù tên sẽ trở nên phụ thuộc, tuyên bố sẽ không được tìm thấy bởi vì ADL sẽ không làm bất cứ điều gì cho các tên đủ điều kiện. –

1

Tên phụ thuộc vẫn yêu cầu khai báo chức năng. Bất kỳ khai báo hàm nào với tên đó sẽ làm, nhưng phải có cái gì đó. Trình biên dịch khác sẽ làm cách nào để định hướng một cuộc gọi hàm bị quá tải từ một đối tượng functor sai chính tả? Nói cách khác, việc tìm kiếm một số chức năng của tên đó, do đó xác minh rằng quá tải về tên đó là có thể, xúi giục quy trình giải quyết quá tải trong tra cứu tên đủ điều kiện (hoặc không đủ tiêu chuẩn).

// hello.cpp 

#include <vector> 

void bar(); // Comeau bails without this. 

template<typename TT> void foo(std::vector<TT*> &vec) 
{ 
    TT *tt; 
    ::bar(tt); 
    vec.push_back(tt); 
} 

class Blah 
{ 
}; 

void bar(Blah *&) 
{ 
} 

int main(int argc, char *argv[]) 
{ 
    std::vector<Blah*> vec; 
    //foo(vec); - instanting it is certainly an error! 

    return 0; 
} 
+0

Theo gcc 4.4.3, mã của bạn đưa ra lỗi '" quá nhiều đối số để hoạt động 'void bar()' "' cho dòng 10 ':: bar (tt)'. Tôi quan tâm đến ý tưởng của bạn rằng thanh biểu tượng cần được ràng buộc một phần cho một gia đình các hàm bị quá tải trong khi phân tích mẫu, nhưng tôi thấy rằng hành vi đó đáng ngạc nhiên nếu đúng, và ít nhất theo gcc mã của tôi hoạt động nếu ':: 'bị xóa. (Mã này là một ví dụ đơn giản, cho mã thực sự của tôi, tôi cần '::' để không ai bận tâm đề nghị điều đó, làm ơn.) –

+0

@Shawn: Không biết phải nói gì. Tôi đã không thử GCC, bình luận của tôi dựa trên việc đọc phần đó của tiêu chuẩn gần đây và thử trên Comeau. Tôi biết rằng đó là bất hợp pháp mà không có bất kỳ tuyên bố nào. Tôi không chắc liệu GCC có được phép cho bạn 'quá nhiều đối số', sẽ phải quay lại và đọc kỹ hơn. (Không có kế hoạch: v (.) – Potatoswatter

2

xấu xa này làm việc với Intel C++ 11.0 và có lẽ làm sáng tỏ quan điểm của trình biên dịch của xem:

#include <vector> 
#include <iostream> 

// ********************* 
// forward declare in the global namespace a family of functions named bar 
// taking some argument whose type is still a matter of speculation 
// at this point 
template<class T> 
void bar(T x); 
// ********************* 

template<typename TT> 
void foo(std::vector<TT*> &vec) 
{ 
    TT *tt; 
    ::bar(tt); 
    vec.push_back(tt); 
} 

class Blah 
{ 
    public: 
}; 

void bar(Blah *x) 
{ 
    // I like output in my examples so I added this 
    std::cout << "Yoo hoo!" << std::endl; 
} 

// ********************** 
// Specialize bar<Blah*> 
template<> 
inline 
void bar<Blah*>(Blah *x) { ::bar(x); } 
// ********************** 

int main(int, char *) 
{ 
    std::vector<Blah*> vec; 
    foo(vec); 

    return 0; 
} 
Các vấn đề liên quan