Làm thế nào để các kiểu không xác định có thể được sử dụng trong biểu thức C ++ 20 'request'?
Tôi đang cố gắng viết một khái niệm C ++ 20 để thể hiện yêu cầu rằng một kiểu phải có một phương thức nhất định, có một đối số, nhưng với mục đích của khái niệm này, tôi không quan tâm kiểu đối số là gì.
Tôi đã cố gắng viết một cái gì đó như:
template <typename T>
concept HasFooMethod = requires(T t, auto x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
tuy nhiên, cả gcc và clang đều từ chối điều này, dẫn đến lỗi không thể sử dụng 'auto' trong danh sách tham số của biểu thức yêu cầu theo cách này.
Một giải pháp thay thế sẽ là đặt loại 'x' làm tham số mẫu thứ hai:
template <typename T, typename TX>
concept HasFooMethod = requires(T t, TX x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
nhưng sau đó điều này yêu cầu TX phải được chỉ định rõ ràng bất cứ khi nào khái niệm được sử dụng, nó không thể được suy ra:
struct S { void Foo(int); };
static_assert(HasFooMethod<S>); // doesn't compile
static_assert(HasFooMethod<S, int>); // the 'int' must be specified
Có cách nào để viết một khái niệm cho phép Foo lấy một đối số thuộc loại không xác định không ?
Câu hỏi Định nghĩa khái niệm yêu cầu hàm thành viên mẫu bị ràng buộc rất giống nhau, nhưng không giống nhau: câu hỏi đó hỏi cách yêu cầu một phương thức (mẫu) có thể nhận bất kỳ kiểu nào thỏa mãn một khái niệm nhất định, trong khi câu hỏi này là yêu cầu một phương thức có một số loại cụ thể, mặc dù loại đó là không xác định. Về định lượng, câu hỏi còn lại hỏi về định lượng phổ quát (có giới hạn) trong khi câu hỏi này là về định lượng hiện sinh. Câu trả lời của câu hỏi khác cũng không áp dụng cho trường hợp của tôi.
Trả lời
Các khái niệm không nhằm cung cấp loại chức năng bạn đang tìm kiếm. Vì vậy, họ không cung cấp nó.
Một khái niệm có nghĩa là ràng buộc các khuôn mẫu, để chỉ định một tập hợp các biểu thức hoặc câu lệnh mà một khuôn mẫu dự định sử dụng (hoặc ít nhất là miễn phí sử dụng) trong định nghĩa của nó.
Trong khuôn mẫu mà bạn đang hạn chế, nếu bạn viết biểu thức t.Foo(x)
, thì bạn biết loại của x
. Nó là một kiểu cụ thể, một tham số mẫu hoặc một tên bắt nguồn từ một tham số mẫu. Dù bằng cách nào, loại x
có sẵn tại mẫu đang bị hạn chế.
Vì vậy, nếu bạn muốn giới hạn một mẫu như vậy, bạn sử dụng cả kiểu của t
và kiểu của x
. Cả hai đều có sẵn cho bạn tại thời điểm đó, vì vậy không có vấn đề gì với việc tạo ra một ràng buộc như vậy. Đó là, ràng buộc không được bật T
như một loại biệt lập; nó nằm trên sự kết hợp giữa T
và X
.
Các khái niệm không có nghĩa là hoạt động trong môi trường chân không, không có bất kỳ liên kết nào với nơi sử dụng thực tế của ràng buộc. Bạn không nên tập trung vào việc tạo ra các khái niệm đơn phân để người dùng có thể static_assert
chống lại các lớp của họ. Các khái niệm không có nghĩa là để kiểm tra nếu một kiểu đáp ứng chúng (về cơ bản là những gì bạn static_assert
đang làm); chúng có nghĩa là để hạn chế định nghĩa mẫu sử dụng chúng.
Sự ràng buộc của bạn cần phải được FooCallableWith
, không phải HasFooMethod
.
Điều gì đó gần với điều này có thể được thực hiện bằng cách xác định một loại bộ điều hợp có thể chuyển đổi ngầm thành (hầu hết) bất kỳ thứ gì:
struct anything
{
// having both these conversions allows Foo's argument to be either
// a value, an lvalue reference, or an rvalue reference
template <typename T>
operator T&();
template <typename T>
operator T&&();
};
Lưu ý rằng các toán tử này không cần phải được triển khai, vì chúng sẽ chỉ được sử dụng trong ngữ cảnh không được đánh giá (và thực sự, chúng không thể được triển khai cho tất cả các kiểu T).
Sau đó, HasFooMethod
có thể được viết là:
template <typename T>
concept HasFooMethod = requires(T t, anything a)
{
{ t.Foo(a) } -> std::same_as<void>;
};