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)...);
}
};
Và 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::fun
và decorator::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!
@Nawaz: Cảm ơn bạn đã biết mẹo. Ở đây, nó làm cho không có sự khác biệt. – akim
'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 –
Để 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