11

Tôi đang cố gắng ra các mã được trình bày bởi Sean Chánh tại buổi nói chuyện tại GoingNative 2.013-"Inheritance is the base class of evil". (mã từ slide cuối cùng có sẵn tại https://gist.github.com/berkus/7041546Tại sao trình biên dịch chọn quá tải hàm không chính xác trong trường hợp này?

tôi đã cố gắng để đạt được cùng một mục tiêu riêng của tôi, nhưng tôi có thể' t hiểu tại sao đoạn code dưới đây sẽ không hành động như tôi mong đợi nó.

#include <boost/smart_ptr.hpp> 
#include <iostream> 
#include <ostream> 

template <typename T> 
void draw(const T& t, std::ostream& out) 
{ 
    std::cout << "Template version" << '\n'; 
    out << t << '\n'; 
} 

class object_t 
{ 
public: 
    template <typename T> 
    explicit object_t (T rhs) : self(new model<T>(rhs)) {}; 

    friend void draw(const object_t& obj, std::ostream& out) 
    { 
     obj.self->draw(out); 
    } 

private: 
    struct concept_t 
    { 
     virtual ~concept_t() {}; 
     virtual void draw(std::ostream&) const = 0; 
    }; 

    template <typename T> 
    struct model : concept_t 
    { 
     model(T rhs) : data(rhs) {}; 
     void draw(std::ostream& out) const 
     { 
      ::draw(data, out); 
     } 

     T data; 
    }; 

    boost::scoped_ptr<concept_t> self; 
}; 

class MyClass {}; 

void draw(const MyClass&, std::ostream& out) 
{ 
    std::cout << "MyClass version" << '\n'; 
    out << "MyClass" << '\n'; 
} 

int main() 
{ 
    object_t first(1); 
    draw(first, std::cout); 

    const object_t second((MyClass())); 
    draw(second, std::cout); 

    return 0; 
} 

phiên bản này xử lý in int tốt, nhưng thất bại trong việc biên dịch trong trường hợp thứ hai là trình biên dịch không biết làm thế nào để sử dụng MyClass với operator<<.Tôi không thể hiểu tại sao trình biên dịch sẽ không chọn quá tải thứ hai được cung cấp riêng cho th e MyClass. Mã biên dịch và hoạt động tốt nếu tôi thay đổi tên của phương thức :: draw() và xóa số tham chiếu không gian tên toàn cầu :: khỏi cơ thể của nó hoặc nếu tôi thay đổi chức năng vẽ toàn cầu của MyClass thành một chuyên môn mẫu hoàn chỉnh.

Thông báo lỗi tôi nhận được là như dưới đây, sau đó là một loạt các candidate function not viable...

t76_stack_friend_fcn_visibility.cpp:9:9: error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'const MyClass') 
    out << t << '\n'; 
    ~~~^~ 
t76_stack_friend_fcn_visibility.cpp:36:15: note: in instantiation of function template specialization 'draw<MyClass>' requested here 
      ::draw(data, out); 
      ^
t76_stack_friend_fcn_visibility.cpp:33:9: note: in instantiation of member function 'object_t::model<MyClass>::draw' requested here 
     model(T rhs) : data(rhs) {}; 
     ^
t76_stack_friend_fcn_visibility.cpp:16:42: note: in instantiation of member function 'object_t::model<MyClass>::model' requested here 
    explicit object_t (T rhs) : self(new model<T>(rhs)) {}; 
             ^
t76_stack_friend_fcn_visibility.cpp:58:20: note: in instantiation of function template specialization 'object_t::object_t<MyClass>' requested here 
    const object_t second((MyClass())); 
       ^

Tại sao phiên bản mẫu của toàn cầu bốc mẫu chức năng choosen trong tình trạng quá tải chức năng MyClass? Có phải vì tham chiếu mẫu là tham lam? Làm thế nào để giải quyết vấn đề này?

+0

Tôi đã thử mã của bạn với MSVC13 và nó biên dịch tốt, sử dụng phiên bản int trong trường hợp đầu tiên và phiên bản MyClass với phiên bản thứ hai. Bạn nên thêm thông tin về trình biên dịch bạn đang sử dụng – Christophe

+0

Tôi sử dụng tính năng chuyển đổi tiếng kêu phiên bản 3.5.0 (thẻ/RELEASE_350/final). –

+0

[Câu hỏi khác có cùng nguyên tắc] (http://stackoverflow.com/questions/8501294/different-behavior-for-qualified-and-unqualified-name-lookup-for-template/8501421#8501421) –

Trả lời

8

Vì bạn sử dụng tên đủ điều kiện trong cuộc gọi chức năng. [Temp.dep.candidate]:

Đối với một cuộc gọi chức năng mà phụ thuộc vào một tham số mẫu, các chức năng ứng cử viên được tìm thấy bằng cách sử dụng quy tắc tra cứu thông thường (3.4.1, 3.4.2 , 3.4.3) ngoại trừ:

  • Đối với một phần của tra cứu sử dụng tra cứu tên không đủ tiêu chuẩn (3.4.1) hay tra cứu tên đủ điều kiện (3.4.3), chỉ có tờ khai chức năng từ bối cảnh nét mẫu được tìm thấy.
  • Đối với phần tra cứu bằng cách sử dụng không gian tên được liên kết (3.4.2), chỉ có các khai báo chức năng được tìm thấy trong định nghĩa mẫu ngữ cảnh hoặc bối cảnh instantiation mẫu được tìm thấy.

§3.4.2 (bí danh [basic.lookup.argdep]):

Khi postfix thể hiện trong một cuộc gọi chức năng (5.2.2) là một không đủ tiêu chuẩn -id, không gian tên khác không được xem xét trong số tra cứu không đủ tiêu chuẩn (3.4.1) có thể được tìm kiếm và trong các không gian tên đó, khai báo hàm bạn bè không gian tên (11.3) không khácCó thể tìm thấy.

Vì vậy, về cơ bản ADL không áp dụng kể từ khi cuộc gọi sử dụng id đủ điều kiện.
Như Barry lãm in his answer bạn có thể giải quyết điều này bằng cách thực hiện cuộc gọi không đủ tiêu chuẩn:

void draw(std::ostream& out) const 
{ 
    using ::draw; 
    draw(data, out); 
} 

Bạn cần phải thêm một -declaration using trước đó mặc dù. Nếu không, việc tra cứu tên không đủ tiêu chuẩn sẽ tìm thấy hàm thành viên model<>::draw trước khi tìm kiếm các vùng khai báo theo thứ tự tăng dần và sẽ không tìm kiếm thêm nữa. Nhưng không chỉ có vậy - model<>::draw (mà là một thành viên lớp) được tìm thấy tra cứu tên không đủ tiêu chuẩn của tôi, ADL là không gọi, [basic.lookup.argdep]/3:

Hãy X là bộ tra cứu được sản xuất bằng tra cứu không đủ tiêu chuẩn (3.4.1) và cho phép Y là bộ tra cứu được tạo ra bởi tra cứu phụ thuộc đối số (được định nghĩa như sau). Nếu X chứa

  • một tuyên bố của một thành viên lớp, hoặc
  • một khai báo hàm khối phạm vi đó không phải là một sử dụng-khai, hoặc
  • một tuyên bố rằng không phải là một chức năng hay mẫu chức năng

rồi Y trống. Nếu không, Y là tập hợp các khai báo được tìm thấy trong các không gian tên được liên kết với các loại đối số như được mô tả bên dưới.

Do đó, nếu using -declaration được cung cấp tờ khai chỉ được tìm thấy bằng cách tra cứu tên không đủ tiêu chuẩn sẽ là toàn cầu draw mẫu đã được đưa vào khu vực khai báo của model::draw. ADL sau đó được gọi và tìm hàm sau được khai báo draw cho MyClass const&.

+1

Bạn có biết biện minh cho điểm đạn đầu tiên? –

+0

@SebastianKramer Đây là cái gọi là "tra cứu hai pha". Hãy xem xét 'void foo (void *); mẫu thanh rỗng (T/* giả * /) {foo (0); } void foo (int); int main() {bar (0); } 'Có lẽ tác giả của khuôn mẫu dự định' bar' để gọi quá tải 'void *' thay vì quá tải 'int'. –

+0

@ T.C.Bạn có chắc chắn rằng thực tế nó hoạt động với khai báo 'using' là tuân thủ tiêu chuẩn? Tôi có một chút lo lắng về [namespace.udecl]/11 – Columbo

3

Khi bạn gọi trực tiếp ::draw(), bạn không thể sử dụng ADL chính xác. Tôi không thực sự biết cụ thể và hy vọng ai đó sẽ đến và giải thích điều này cho tôi quá [chỉnh sửa: xem Columbo's answer với lý do]) Nhưng để thực sự sử dụng ADL, bạn cần thực hiện cuộc gọi không đủ tiêu chuẩn draw giống như vậy:

void draw(std::ostream& out) const 
{ 
    using ::draw; 
    draw(data, out); 
} 

Điều đó sẽ tìm thấy tình trạng quá tải draw(const MyClass&, std::ostream&).

+0

Mở rộng phạm vi tìm kiếm giúp, cảm ơn. Tuy nhiên tôi đang tìm kiếm sự hợp lý đằng sau hành vi này. –

+0

@SebastianKramer tốt, ADL không thể áp dụng trong ngữ cảnh định nghĩa bởi vì chúng tôi không biết những gì các đối số hoặc các chức năng ứng cử viên là có. Và điều mong muốn là ADL áp dụng trong bối cảnh instantiation (vì những lý do tương tự mà [ADL là mong muốn bên ngoài các mẫu] (http://stackoverflow.com/questions/8111677/what-is-argument-dependent-lookup-aka -adl-or-koenig-lookup)). Điều đó để lại cho chúng ta nguyên trạng. –

+0

Mặc dù tôi chưa thấy lý do tại sao các tra cứu 3.4.1 không nên được áp dụng trong bối cảnh instantiation cho một cuộc gọi chức năng phụ thuộc. –

Các vấn đề liên quan