Tại sao C ++ không thể suy ra T trong một cuộc gọi tới Foo <T> :: Foo (T &&)?


9

Cho mẫu cấu trúc sau:

template<typename T>
struct Foo {
    Foo(T&&) {}
};

Điều này biên dịch, và Tđược suy luận là int:

auto f = Foo(2);

Nhưng điều này không được biên dịch: https://godbolt.org/z/hAA9TE

int x = 2;
auto f = Foo(x);

/*
<source>:12:15: error: no viable constructor or deduction guide for deduction of template arguments of 'Foo'
    auto f = Foo(x);
             ^

<source>:7:5: note: candidate function [with T = int] not viable: no known conversion from 'int' to 'int &&' for 1st argument
    Foo(T&&) {}
    ^
*/

Tuy nhiên, Foo<int&>(x)được chấp nhận.

Nhưng khi tôi thêm một hướng dẫn khấu trừ do người dùng định nghĩa dường như dư thừa, nó sẽ hoạt động:

template<typename T>
Foo(T&&) -> Foo<T>;

Tại sao không Tthể suy luận như int&không có hướng dẫn khấu trừ do người dùng xác định?



Câu hỏi đó dường như là về các loại mẫu nhưFoo<T<A>>
jtbandes

Câu trả lời:


5

Tôi nghĩ rằng sự nhầm lẫn ở đây phát sinh bởi vì có một ngoại lệ cụ thể cho các hướng dẫn khấu trừ tổng hợp liên quan đến các tài liệu tham khảo chuyển tiếp.

Đúng là hàm ứng cử viên cho mục đích khấu trừ đối số mẫu lớp được tạo từ hàm tạo và hàm được tạo từ hướng dẫn khấu trừ do người dùng định nghĩa trông giống hệt nhau, nghĩa là:

template<typename T>
auto f(T&&) -> Foo<T>;

nhưng đối với cái được tạo từ hàm tạo, T&&là một tham chiếu giá trị đơn giản, trong khi nó là một tham chiếu chuyển tiếp trong trường hợp do người dùng định nghĩa. Điều này được chỉ định bởi [temp.deduct.call] / 3 của tiêu chuẩn C ++ 17 (dự thảo N4659, nhấn mạnh của tôi):

Một tài liệu tham khảo giao nhận là một tài liệu tham khảo rvalue một mẫu tham số cv-không đủ điều kiện mà không đại diện cho một mẫu tham số của một lớp mẫu (trong trích lập luận lớp mẫu ([over.match.class.deduct])).

Do đó, ứng cử viên được tổng hợp từ hàm tạo của lớp sẽ không suy ra Tnhư thể từ một tham chiếu chuyển tiếp (có thể suy ra Tlà tham chiếu giá trị, do đó T&&cũng là tham chiếu giá trị), nhưng thay vào đó sẽ chỉ suy ra Tlà không tham chiếu, vì vậy T&&luôn luôn là một tài liệu tham khảo giá trị.


1
Cảm ơn bạn đã trả lời rõ ràng và súc tích. Bạn có biết tại sao có một ngoại lệ cho các tham số mẫu lớp trong các quy tắc để chuyển tiếp tham chiếu không?
jtbandes

2
@jtbandes Điều này dường như đã được thay đổi do một bình luận của cơ quan quốc gia Hoa Kỳ, xem bài viết p0512r0 . Tôi không thể tìm thấy bình luận mặc dù. Tôi đoán cho lý do là nếu bạn viết một hàm tạo lấy tham chiếu giá trị, bạn thường mong đợi nó hoạt động theo cách tương tự cho dù bạn chỉ định một Foo<int>(...)hoặc chỉ Foo(...), không phải là trường hợp với các tham chiếu chuyển tiếp (có thể suy ra Foo<int&>thay vào đó
walnut

6

Vấn đề ở đây là, vì lớp được tạo khuôn mẫu T, trong hàm tạo, Foo(T&&)chúng ta không thực hiện khấu trừ kiểu; Chúng tôi luôn có một tài liệu tham khảo giá trị r. Đó là, hàm tạo Foothực sự trông như thế này:

Foo(int&&)

Foo(2)hoạt động vì 2là một giá trị.

Foo(x)không phải vì xlà một giá trị không thể liên kết int&&. Bạn có thể làm std::move(x)để chuyển nó sang loại thích hợp ( bản demo )

Foo<int&>(x)hoạt động tốt chỉ vì các nhà xây dựng trở thành Foo(int&)do các quy tắc sụp đổ tham chiếu; ban đầu nó Foo((int&)&&)sụp đổ Foo(int&)theo tiêu chuẩn.

Liên quan đến hướng dẫn khấu trừ "dự phòng" của bạn: Ban đầu có một hướng dẫn khấu trừ mẫu mặc định cho mã về cơ bản hoạt động như một hàm trợ giúp như vậy:

template<typename T>
struct Foo {
    Foo(T&&) {}
};

template<typename T>
Foo<T> MakeFoo(std::add_rvalue_reference_t<T> value)
{
   return Foo<T>(std::move(value));
}

//... 
auto f = MakeFoo(x);

Điều này là do tiêu chuẩn ra lệnh rằng phương thức mẫu (hư cấu) này có cùng tham số mẫu với lớp (Chỉ T) theo sau bởi bất kỳ tham số mẫu nào như hàm tạo (không có trong trường hợp này; hàm tạo không được tạo khuôn mẫu). Sau đó, các loại tham số hàm giống như trong các hàm tạo. Trong trường hợp của chúng tôi, sau khi khởi tạo Foo<int>, hàm tạo trông giống như Foo(int&&)một tham chiếu giá trị trong các từ khác. Do đó việc sử dụng add_rvalue_reference_tở trên.

Rõ ràng điều này không hoạt động.

Khi bạn thêm hướng dẫn khấu trừ "dự phòng" của mình:

template<typename T>
Foo(T&&) -> Foo<T>;

Bạn cho phép trình biên dịch để phân biệt rằng, mặc dù bất kỳ loại tài liệu tham khảo kèm theo Ttrong constructor ( int&, const int&hoặc int&&vv), bạn dự định loại suy ra cho lớp để được mà không có tài liệu tham khảo (chỉ T). Điều này là do chúng ta đột nhiên đang thực hiện suy luận kiểu.

Bây giờ chúng ta tạo một hàm trợ giúp (hư cấu) khác trông như thế này:

template<class U>
Foo<U> MakeFoo(U&& u)
{
   return Foo<U>(std::forward<U>(u));
}

// ...
auto f = MakeFoo(x);

(Các lệnh gọi của chúng ta đến hàm tạo được chuyển hướng đến hàm trợ giúp cho mục đích khấu trừ đối số khuôn mẫu lớp, do đó Foo(x)trở thành MakeFoo(x)).

Điều này cho phép U&&trở thành int&Ttrở nên đơn giảnint


Cấp độ thứ hai của templating dường như không cần thiết; nó cung cấp giá trị gì? Bạn có thể cung cấp một liên kết đến một số tài liệu làm rõ lý do tại sao T && luôn được coi là tài liệu tham khảo giá trị ở đây không?
jtbandes

1
Nhưng nếu T chưa được suy luận, "bất kỳ loại chuyển đổi thành T" có nghĩa là gì?
jtbandes

1
Bạn nhanh chóng đưa ra cách giải quyết, nhưng bạn có thể tập trung hơn vào lời giải thích cho lý do tại sao nó không hoạt động trừ khi bạn sửa đổi nó theo một cách nào đó? Tại sao nó không hoạt động như vậy? " xlà một giá trị không thể liên kết với int&&" nhưng ai đó không hiểu sẽ bối rối Foo<int&>(x)có thể làm việc nhưng không được tìm ra tự động - tôi nghĩ tất cả chúng ta đều muốn hiểu sâu hơn về lý do tại sao.
Wyck

2
@Wyck: Tôi đã cập nhật bài viết để tập trung hơn vào lý do.
AndyG

3
@Wyck Lý do thực sự rất đơn giản: tiêu chuẩn đặc biệt tắt ngữ nghĩa chuyển tiếp hoàn hảo trong hướng dẫn khấu trừ tổng hợp để ngăn chặn những điều bất ngờ.
LF
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.