2014-11-03 16 views
24

Nhờ có decltype làm kiểu trả về, C++ 11 làm cho trang trí cực kỳ dễ dàng. Ví dụ, hãy xem xét lớp này:Tại sao loại trả lại tự động thay đổi độ phân giải quá tải?

struct base 
{ 
    void fun(unsigned) {} 
}; 

Tôi muốn để trang trí nó với các tính năng bổ sung, và kể từ khi tôi sẽ làm điều đó nhiều lần với các loại khác nhau của đồ trang trí, tôi lần đầu tiên giới thiệu một lớp decorator mà chỉ đơn giản chuyển tiếp tất cả mọi thứ để base. Trong mã thực, điều này được thực hiện thông qua std::shared_ptr để tôi có thể xóa đồ trang trí và khôi phục đối tượng "khỏa thân" và mọi thứ được tô điểm.

#include <utility> // std::forward 
struct decorator 
{ 
    base b; 

    template <typename... Args> 
    auto 
    fun(Args&&... args) 
    -> decltype(b.fun(std::forward<Args>(args)...)) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

Chuyển tiếp hoàn hảo và decltype thật tuyệt vời. Trong mã thực, tôi thực sự sử dụng một macro chỉ cần tên của hàm, tất cả các phần còn lại là boilerplate.

Và sau đó, tôi có thể giới thiệu một lớp derived có thêm tính năng để đối tượng của tôi (derived là không đúng, đồng ý, nhưng nó giúp hiểu biết rằng derived is-a-loại base, mặc dù không thông qua thừa kế).

struct foo_t {}; 
struct derived : decorator 
{ 
    using decorator::fun; // I want "native" fun, and decorated fun. 
    void fun(const foo_t&) {} 
}; 

int main() 
{ 
    derived d; 
    d.fun(foo_t{}); 
} 

Sau đó, C++ 14 đến, với kiểu trả về khấu trừ, cho phép để viết những điều theo một cách đơn giản hơn: loại bỏ các decltype phần của chức năng chuyển tiếp:

struct decorator 
{ 
    base b; 

    template <typename... Args> 
    auto 
    fun(Args&&... args) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

sau đó nó nghỉ giải lao. Vâng, ít nhất theo cả GCC và Clang, đây:

template <typename... Args> 
    auto 
    fun(Args&&... args) 
    -> decltype(b.fun(std::forward<Args>(args)...)) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

không tương đương với này (và vấn đề này không phải là auto vs decltype(auto)):

template <typename... Args> 
    auto 
    fun(Args&&... args) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

Nghị quyết tình trạng quá tải có vẻ là hoàn toàn khác nhau và nó kết thúc như thế này:

clang++-mp-3.5 -std=c++1y main.cc 
main.cc:19:18: error: no viable conversion from 'foo_t' to 'unsigned int' 
    return b.fun(std::forward<Args>(args)...); 
       ^~~~~~~~~~~~~~~~~~~~~~~~ 
main.cc:32:5: note: in instantiation of function template specialization 
     'decorator::fun<foo_t>' requested here 
    d.fun(foo_t{}); 
    ^
main.cc:7:20: note: passing argument to parameter here 
    void fun(unsigned) {} 
       ^

tôi hiểu sự thất bại: kêu gọi của Mẹ (d.fun(foo_t{})) không khớp hoàn hảo với chữ ký của derived::fun, mất const foo_t&, vì vậy rất mong muốn decorator::fun đá vào (chúng tôi biết cách Args&&... cực kỳ thiếu kiên nhẫn để ràng buộc với bất kỳ điều gì không khớp hoàn hảo). Vì vậy, nó chuyển tiếp này đến base::fun không thể xử lý với foo_t.

Nếu tôi thay đổi derived::fun để có một foo_t thay vì const foo_t&, sau đó nó hoạt động như mong đợi, trong đó cho thấy rằng thực sự ở đây vấn đề là có sự cạnh tranh giữa derived::fundecorator::fun.

Tuy nhiên tại sao điều này lại hiển thị với khấu trừ kiểu trả về ??? Và chính xác hơn là lý do tại sao là hành vi này do ủy ban lựa chọn?

Để làm cho mọi việc dễ dàng hơn, trên Coliru:

Cảm ơn!

+0

@Nawaz: Cảm ơn bạn đã biết mẹo. Ở đây, nó làm cho không có sự khác biệt. – akim

+7

'decltype (b.fun (std :: forward (args) ...))' trong kiểu trả về theo sau hoạt động như * Biểu thức SFINAE *, trong khi cả 'auto' lẫn' decltype (auto) 'không –

+4

Để thêm vào Ý kiến ​​của Piotr S.: đó cũng là chủ ý. Để SFINAE hoạt động, kiểu trả về phải có sẵn mà không cần nhìn vào thân hàm. Để kích hoạt lambdas trở về, kiểu trả về không được có sẵn mà không cần nhìn vào phần thân hàm. Một quyết định được đưa ra là việc cho phép trở về lambdas quan trọng hơn SFINAE. – hvd

Trả lời

18

Chỉ cần nhìn vào cuộc gọi này:

d.fun(foo_t{}); 

Bạn tạo một tạm thời (nghĩa là rvalue), đi qua nó như là đối số cho hàm. Bây giờ bạn nghĩ điều gì sẽ xảy ra?

  • Nó đầu tiên nỗ lực liên kết với đối số Arg&&, vì nó có thể chấp nhận rvalue nhưng do không hợp lệ kiểu trả về khấu trừ (mà một lần nữa do foo_t không thể chuyển đổi thành unsigned int, bởi vì trong đó b.fun(std::forward<Args>(args)...) lượt biểu hiện không hợp lệ), chức năng này bị từ chối nếu bạn sử dụng decltype(expr) làm loại trả lại, như trong trường hợp này SFINAE được đưa vào ảnh. Nhưng nếu bạn sử dụng chỉ đơn giản là auto, thì SFINAE không được đưa vào hình ảnh và lỗi được phân loại là lỗi cứng dẫn đến lỗi biên dịch.

  • Quá tải thứ hai chấp nhận foo_t const& làm đối số được gọi nếu SFINAE hoạt động trong trường hợp đầu tiên.

+1

Ah, tôi đã không nhận ra rằng C++ 11 đã làm việc vì lệnh gọi 'base :: fun' bị từ chối, và _then_' derived :: fun' được chọn là lựa chọn thứ hai. Cảm ơn rất nhiều! Tuy nhiên, làm thế nào đến 'decltype (tự động)' không khắc phục được vấn đề? Nó có nên được coi là một lỗi trong cả GCC và Clang? – akim

+1

OK, tôi chỉ đọc nhận xét của bạn cho chính câu hỏi đó và làm cho mọi thứ rõ ràng, cảm ơn nhiều! – akim

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