Chức năng quá tải theo kiểu trả về?


252

Tại sao các ngôn ngữ chính được gõ tĩnh hơn hỗ trợ chức năng / phương thức nạp chồng theo kiểu trả về? Tôi không thể nghĩ ra cái nào làm được. Có vẻ như không hữu ích hoặc hợp lý hơn là hỗ trợ quá tải theo loại tham số. Tại sao nó lại ít phổ biến hơn?


Câu trả lời:


523

Trái với những gì người khác đang nói, quá tải theo kiểu trả về có thể và được thực hiện bởi một số ngôn ngữ hiện đại. Sự phản đối thông thường là trong mã như

int func();
string func();
int main() { func(); }

bạn không thể biết cái nào func()đang được gọi. Điều này có thể được giải quyết theo một số cách:

  1. Có một phương pháp dự đoán để xác định hàm nào được gọi trong tình huống như vậy.
  2. Bất cứ khi nào một tình huống như vậy xảy ra, đó là một lỗi thời gian biên dịch. Tuy nhiên, có một cú pháp cho phép lập trình viên định hướng, ví dụ int main() { (string)func(); }.
  3. Đừng có tác dụng phụ. Nếu bạn không có tác dụng phụ và bạn không bao giờ sử dụng giá trị trả về của hàm, thì trình biên dịch có thể tránh việc gọi hàm ở vị trí đầu tiên.

Hai trong số các ngôn ngữ tôi thường xuyên ( ab ) sử dụng quá tải theo kiểu trả về: PerlHaskell . Hãy để tôi mô tả những gì họ làm.

Trong Perl , có một sự phân biệt cơ bản giữa bối cảnh vô hướngdanh sách (và các bối cảnh khác, nhưng chúng ta sẽ giả vờ có hai). Mỗi hàm tích hợp trong Perl có thể làm những việc khác nhau tùy thuộc vào ngữ cảnh mà nó được gọi. Ví dụ, jointoán tử buộc liệt kê bối cảnh (trên vật được nối) trong khi scalartoán tử buộc bối cảnh vô hướng, vì vậy hãy so sánh:

print join " ", localtime(); # printed "58 11 2 14 0 109 3 13 0" for me right now
print scalar localtime(); # printed "Wed Jan 14 02:12:44 2009" for me right now.

Mỗi toán tử trong Perl làm một cái gì đó trong bối cảnh vô hướng và một cái gì đó trong bối cảnh danh sách, và chúng có thể khác nhau, như được minh họa. (Điều này không chỉ dành cho các toán tử ngẫu nhiên như localtime. Nếu bạn sử dụng một mảng @atrong ngữ cảnh danh sách, nó sẽ trả về mảng, trong khi ở ngữ cảnh vô hướng, nó sẽ trả về số lượng phần tử. Ví dụ, print @ain ra các phần tử, trong khi print 0+@ain kích thước. ) Hơn nữa, mọi toán tử có thể buộc một bối cảnh, ví dụ như bổ sung các +lực vô hướng. Mỗi mục trong man perlfunctài liệu này. Ví dụ, đây là một phần của mục nhập cho glob EXPR:

Trong ngữ cảnh danh sách, trả về một danh sách (có thể trống) các mở rộng tên tệp trên giá trị EXPRnhư shell Unix tiêu chuẩn /bin/cshsẽ làm. Trong bối cảnh vô hướng, global lặp đi lặp lại thông qua các mở rộng tên tệp như vậy, trả về undef khi danh sách đã hết.

Bây giờ, những gì liên quan giữa danh sách và bối cảnh vô hướng? Vâng, man perlfuncnói

Hãy nhớ quy tắc quan trọng sau: Không có quy tắc nào liên quan đến hành vi của một biểu thức trong ngữ cảnh danh sách với hành vi của nó trong ngữ cảnh vô hướng hoặc ngược lại. Nó có thể làm hai việc hoàn toàn khác nhau. Mỗi toán tử và hàm quyết định loại giá trị nào sẽ phù hợp nhất để trả về trong bối cảnh vô hướng. Một số toán tử trả về độ dài của danh sách sẽ được trả về trong ngữ cảnh danh sách. Một số toán tử trả về giá trị đầu tiên trong danh sách. Một số toán tử trả về giá trị cuối cùng trong danh sách. Một số nhà khai thác trả lại một số lượng các hoạt động thành công. Nói chung, họ làm những gì bạn muốn, trừ khi bạn muốn sự nhất quán.

Vì vậy, đó không phải là vấn đề đơn giản khi có một chức năng duy nhất và sau đó bạn thực hiện chuyển đổi đơn giản vào cuối. Trong thực tế, tôi đã chọn localtimeví dụ cho lý do đó.

Nó không chỉ là các phần mềm tích hợp có hành vi này. Bất kỳ người dùng nào cũng có thể định nghĩa một hàm như vậy bằng cách sử dụng wantarray, cho phép bạn phân biệt giữa danh sách, vô hướng và bối cảnh trống. Vì vậy, ví dụ, bạn có thể quyết định không làm gì nếu bạn bị gọi trong bối cảnh trống.

Bây giờ, bạn có thể phàn nàn rằng đây không phải quá tải thực sự bởi giá trị trả về vì bạn chỉ có một chức năng, được cho biết bối cảnh mà nó được gọi và sau đó hành động theo thông tin đó. Tuy nhiên, điều này rõ ràng tương đương (và tương tự như cách Perl không cho phép quá tải thông thường theo nghĩa đen, nhưng một hàm chỉ có thể kiểm tra các đối số của nó). Hơn nữa, nó giải quyết tốt tình huống mơ hồ được đề cập ở phần đầu của phản ứng này. Perl không phàn nàn rằng nó không biết nên gọi phương thức nào; nó chỉ gọi nó Tất cả những gì nó phải làm là tìm ra bối cảnh mà hàm được gọi trong đó, điều này luôn luôn có thể:

sub func {
    if( not defined wantarray ) {
        print "void\n";
    } elsif( wantarray ) {
        print "list\n";
    } else {
        print "scalar\n";
    }
}

func(); # prints "void"
() = func(); # prints "list"
0+func(); # prints "scalar"

(Lưu ý: Đôi khi tôi có thể nói toán tử Perl khi tôi muốn nói đến chức năng. Điều này không quan trọng đối với cuộc thảo luận này.)

Haskell có cách tiếp cận khác, cụ thể là không có tác dụng phụ. Nó cũng có một hệ thống loại mạnh và vì vậy bạn có thể viết mã như sau:

main = do n <- readLn
          print (sqrt n) -- note that this is aligned below the n, if you care to run this

Mã này đọc một số dấu phẩy động từ đầu vào tiêu chuẩn và in căn bậc hai của nó. Nhưng điều gì đáng ngạc nhiên về điều này? Vâng, loại readLnreadLn :: Read a => IO a. Điều này có nghĩa là đối với bất kỳ loại nào có thể Read(chính thức, mọi loại là một thể hiện của Readlớp loại), readLnđều có thể đọc nó. Làm thế nào mà Haskell biết rằng tôi muốn đọc một số dấu phẩy động? Vâng, kiểu sqrtsqrt :: Floating a => a -> a, trong đó chủ yếu có nghĩa rằng sqrtchỉ có thể chấp nhận số dấu phảy động đầu vào, và do đó Haskell suy ra những gì tôi muốn.

Điều gì xảy ra khi Haskell không thể suy ra những gì tôi muốn? Vâng, có một vài khả năng. Nếu tôi hoàn toàn không sử dụng giá trị trả về, Haskell chỉ đơn giản là không gọi hàm ở vị trí đầu tiên. Tuy nhiên, nếu tôi làm sử dụng giá trị trả về, sau đó Haskell sẽ phàn nàn rằng nó không thể suy ra các loại:

main = do n <- readLn
          print n
-- this program results in a compile-time error "Unresolved top-level overloading"

Tôi có thể giải quyết sự mơ hồ bằng cách chỉ định loại tôi muốn:

main = do n <- readLn
          print (n::Int)
-- this compiles (and does what I want)

Dù sao, toàn bộ cuộc thảo luận này có nghĩa là quá tải bởi giá trị trả về là có thể và được thực hiện, điều này trả lời một phần câu hỏi của bạn.

Phần khác của câu hỏi của bạn là tại sao nhiều ngôn ngữ không làm điều đó. Tôi sẽ để người khác trả lời. Tuy nhiên, một vài ý kiến: lý do chính có lẽ là cơ hội cho sự nhầm lẫn ở đây thực sự lớn hơn so với việc quá tải theo loại đối số. Bạn cũng có thể xem các lý do từ các ngôn ngữ riêng lẻ:

Ada : "Có vẻ như quy tắc giải quyết quá tải đơn giản nhất là sử dụng mọi thứ - tất cả thông tin từ bối cảnh càng rộng càng tốt - để giải quyết tham chiếu quá tải. Quy tắc này có thể đơn giản, nhưng không hữu ích. để quét các đoạn văn bản lớn tùy ý và tạo ra các suy luận phức tạp tùy ý (chẳng hạn như (g) ở trên). Chúng tôi tin rằng một quy tắc tốt hơn là một quy tắc làm rõ ràng nhiệm vụ mà người đọc hoặc trình biên dịch phải thực hiện và điều đó thực hiện nhiệm vụ này càng tự nhiên cho người đọc càng tốt. "

C ++ (tiểu mục 7.4.1of "Ngôn ngữ lập trình C ++" của Bjarne Stroustrup): "Các kiểu trả về không được xem xét trong độ phân giải quá tải. Lý do là để giữ độ phân giải cho một toán tử riêng lẻ hoặc hàm gọi độc lập với ngữ cảnh.

float sqrt(float);
double sqrt(double);

void f(double da, float fla)
{
    float fl = sqrt(da);     // call sqrt(double)
    double d = sqrt(da); // call sqrt(double)
    fl = sqrt(fla);            // call sqrt(float)
    d = sqrt(fla);             // call sqrt(float)
}

Nếu loại trả về đã được tính đến, sẽ không còn có thể xem xét một cuộc gọi sqrt()riêng lẻ và xác định chức năng nào được gọi. "(Lưu ý, để so sánh, trong Haskell không có chuyển đổi ngầm định .)

Java ( Đặc tả ngôn ngữ Java 9.4.1 ): "Một trong các phương thức được kế thừa phải là kiểu trả về thay thế cho mọi phương thức được kế thừa khác, nếu không sẽ xảy ra lỗi thời gian biên dịch." (Vâng, tôi biết điều này không đưa ra một lý do hợp lý. Tôi chắc chắn lý do được đưa ra bởi Gosling trong "Ngôn ngữ lập trình Java". Có lẽ ai đó có một bản sao? Tôi cá là "nguyên tắc ít bất ngờ nhất" về bản chất. ) Tuy nhiên, thực tế thú vị về Java: JVM cho phép quá tải theo giá trị trả về! Điều này được sử dụng, ví dụ, trong Scala và cũng có thể được truy cập trực tiếp thông qua Java bằng cách chơi xung quanh với các phần bên trong.

Tái bút Như một lưu ý cuối cùng, thực sự có thể quá tải bằng giá trị trả về trong C ++ bằng một mẹo. Nhân chứng:

struct func {
    operator string() { return "1";}
    operator int() { return 2; }
};

int main( ) {
    int x    = func(); // calls int version
    string y = func(); // calls string version
    double d = func(); // calls int version
    cout << func() << endl; // calls int version
    func(); // calls neither
}

Bài đăng tuyệt vời, nhưng bạn có thể muốn làm rõ việc đọc là gì (Chuỗi -> một cái gì đó).
Thomas Eding

C ++ cũng cho phép bạn quá tải bởi const / không const giá trị trả về. stackoverflow.com/questions/251159/
trộm

3
Đối với thủ thuật cuối cùng của bạn với việc quá tải các toán tử cưỡng chế, dòng "cout" đôi khi hoạt động, nhưng hầu như bất kỳ thay đổi nào tôi thực hiện đối với mã đều khiến nó "quá tải mơ hồ cho 'toán tử <<'".
Steve

1
Cách tiếp cận tôi muốn là yêu cầu một quá tải được đánh dấu là "ưa thích"; trình biên dịch sẽ bắt đầu bằng cách ràng buộc chỉ sử dụng các quá tải ưa thích và sau đó xác định xem có bất kỳ quá tải không ưu tiên nào sẽ là một sự cải tiến hay không. Trong số những thứ khác, giả sử các loại FooBarhỗ trợ chuyển đổi hai chiều và một phương thức sử dụng loại Foobên trong nhưng trả về loại Bar. Nếu phương thức đó được gọi bằng mã sẽ ngay lập tức ép kết quả thành kiểu Foo, sử dụng Barkiểu trả về có thể hoạt động, nhưng phương thức Foosẽ tốt hơn. BTW, tôi cũng muốn xem một phương tiện mà ...
supercat

... một phương thức có thể chỉ định loại nào sẽ được sử dụng trong một cấu trúc như var someVar = someMethod();(hoặc chỉ định khác rằng không nên sử dụng trả lại của nó theo kiểu như vậy). Ví dụ, một nhóm các loại thực hiện giao diện Fluent có thể được hưởng lợi từ việc có các phiên bản có thể thay đổi và không thay đổi, do đó var thing2 = thing1.WithX(3).WithY(5).WithZ(9);sẽ WithX(3)sao chép thing1vào một đối tượng có thể thay đổi, biến đổi X và trả về đối tượng có thể thay đổi đó; WithY(5)sẽ biến đổi Y và trả lại cùng một đối tượng; tương tự như vậy `WithZ (9). Sau đó, nhiệm vụ sẽ chuyển đổi thành một loại bất biến.
supercat

37

Nếu các hàm bị quá tải bởi kiểu trả về và bạn có hai lần quá tải này

int func();
string func();

không có cách nào trình biên dịch có thể tìm ra hàm nào trong hai hàm đó để gọi khi thấy một cuộc gọi như thế này

void main() 
{
    func();
}

Vì lý do này, các nhà thiết kế ngôn ngữ thường không cho phép quá tải giá trị trả về.

Một số ngôn ngữ (chẳng hạn như MSIL), tuy nhiên, làm phép quá tải bởi kiểu trả về. Họ cũng phải đối mặt với những khó khăn trên, nhưng họ có cách giải quyết, mà bạn sẽ phải tham khảo tài liệu của họ.


4
Một ngụy biện nhỏ (câu trả lời của bạn đưa ra một lý do rất rõ ràng, dễ hiểu): không phải là không có cách nào; chỉ là những cách đó sẽ vụng về và đau đớn hơn hầu hết mọi người muốn. Ví dụ, trong C ++, tình trạng quá tải có thể đã được giải quyết bằng cách sử dụng một số cú pháp cast xấu xí.
Michael Burr

2
@ Jorg W Mittag: Bạn không thấy các chức năng làm gì. Họ có thể dễ dàng có tác dụng phụ khác nhau .
A. Rex

2
@ Jörg - trong hầu hết các ngôn ngữ lập trình chính (C / C ++, C #, Java, v.v.) thường có tác dụng phụ. Trên thực tế, tôi đoán rằng các chức năng có tác dụng phụ ít nhất cũng phổ biến như các chức năng không có.
Michael Burr

6
Nhảy vào cuối ở đây, nhưng trong một số bối cảnh "chức năng" có định nghĩa hẹp về (về cơ bản) "một phương pháp không có tác dụng phụ". Thông thường hơn, "hàm" thường được sử dụng thay thế cho "phương thức" hoặc "chương trình con". Jorg là nghiêm khắc hoặc ấu dâm, tùy thuộc vào quan điểm của bạn :)
AwesomeTown

3
Nhảy vào thậm chí muộn hơn, một số quan điểm có thể sử dụng các tính từ khác ngoài nghiêm ngặt hoặc mang tính mô phạm
Patrick McDonald

27

Trong ngôn ngữ như vậy, bạn sẽ giải quyết như thế nào sau đây:

f(g(x))

nếu fđã quá tải void f(int)void f(string)gđã quá tải int g(int)string g(int)? Bạn sẽ cần một số loại disambiguator.

Tôi nghĩ rằng các tình huống mà bạn có thể cần điều này sẽ được phục vụ tốt hơn bằng cách chọn một tên mới cho hàm.


2
Các loại quá tải thường xuyên cũng có thể dẫn đến sự mơ hồ. Tôi nghĩ rằng những điều này thường được giải quyết bằng cách đếm số lượng phôi yêu cầu, nhưng điều này không phải lúc nào cũng hoạt động.
Jay Conrod

1
có, chuyển đổi tiêu chuẩn được xếp hạng phù hợp chính xác, khuyến mãi và chuyển đổi: void f (int); khoảng trống f (dài); f ('a'); gọi f (int), vì đó chỉ là một khuyến mãi, trong khi chuyển đổi thành dài là chuyển đổi. khoảng trống f (phao); khoảng trống f (ngắn); f (10); sẽ yêu cầu chuyển đổi cho cả hai: cuộc gọi không rõ ràng.
Julian Schaub - litb

Nếu ngôn ngữ có sự đánh giá lười biếng, thì đây không phải là vấn đề.
JDD

Upvote, sự tương tác của quá tải loại tham số và quá tải kiểu trả về không được đề cập trong bài viết của Rex. Điểm rất tốt.
Joseph Garvin

1
Nếu tôi thiết kế một ngôn ngữ, quy tắc của tôi sẽ là đối với bất kỳ hàm quá tải nào, mỗi chữ ký tham số phải có một kiểu trả về được chỉ định làm mặc định; một trình biên dịch sẽ bắt đầu bằng cách giả sử mọi lệnh gọi hàm sẽ sử dụng kiểu mặc định. Tuy nhiên, khi điều đó đã được thực hiện, trong mọi tình huống khi giá trị trả về của hàm ngay lập tức được ép hoặc ép buộc với một thứ khác, trình biên dịch sẽ kiểm tra tình trạng quá tải có chữ ký tham số giống hệt nhau, nhưng kiểu trả về của nó phù hợp hơn (hoặc có thể bị vô hiệu) . Tôi có lẽ cũng sẽ áp đặt quy tắc "ghi đè một - ghi đè tất cả" cho các tình trạng quá tải như vậy.
supercat

19

Để đánh cắp một câu trả lời cụ thể của C ++ từ một câu hỏi rất giống nhau (dupe?):


Các kiểu trả về hàm không xuất hiện ở độ phân giải quá tải đơn giản chỉ vì Stroustrup (tôi giả sử với đầu vào từ các kiến ​​trúc sư C ++ khác) muốn độ phân giải quá tải phải 'độc lập với bối cảnh'. Xem 7.4.1 - "Loại quá tải và trả về" từ "Ngôn ngữ lập trình C ++, Ấn bản thứ ba".

Lý do là để giữ độ phân giải cho một toán tử riêng lẻ hoặc chức năng gọi độc lập theo ngữ cảnh.

Họ muốn nó chỉ dựa trên cách gọi quá tải - chứ không phải cách sử dụng kết quả (nếu nó được sử dụng). Thật vậy, nhiều hàm được gọi mà không sử dụng kết quả hoặc kết quả sẽ được sử dụng như một phần của biểu thức lớn hơn. Một yếu tố mà tôi chắc chắn đã phát huy khi họ quyết định điều này là nếu kiểu trả về là một phần của độ phân giải, sẽ có nhiều lệnh gọi đến các hàm quá tải cần được giải quyết bằng các quy tắc phức tạp hoặc sẽ phải ném trình biên dịch một lỗi mà cuộc gọi không rõ ràng

Và, Lord biết, độ phân giải quá tải C ++ đủ phức tạp khi nó đứng ...


5

Trong haskell, điều đó là có thể mặc dù nó không có chức năng quá tải. Haskell sử dụng các lớp loại. Trong một chương trình bạn có thể thấy:

class Example a where
    example :: Integer -> a

instance Example Integer where  -- example is now implemented for Integer
    example :: Integer -> Integer
    example i = i * 10

Chức năng quá tải bản thân nó không quá phổ biến. Hầu hết các ngôn ngữ tôi đã thấy với nó là C ++, có lẽ java và / hoặc C #. Trong tất cả các ngôn ngữ động, đó là cách viết tắt của:

define example:i
  ↑i type route:
    Integer = [↑i & 0xff]
    String = [↑i upper]


def example(i):
    if isinstance(i, int):
        return i & 0xff
    elif isinstance(i, str):
        return i.upper()

Do đó, không có nhiều điểm trong đó. Hầu hết mọi người không quan tâm liệu ngôn ngữ có thể giúp bạn bỏ một dòng trên mỗi nơi bạn sử dụng ngôn ngữ đó không.

Kết hợp mẫu có phần giống với nạp chồng hàm và tôi đoán đôi khi hoạt động tương tự. Mặc dù vậy, điều này không phổ biến vì nó chỉ hữu ích cho một số chương trình và khó thực hiện trên hầu hết các ngôn ngữ.

Bạn thấy có vô số các tính năng dễ thực hiện khác tốt hơn để triển khai vào ngôn ngữ, bao gồm:

  • Gõ động
  • Hỗ trợ nội bộ cho danh sách, từ điển và chuỗi unicode
  • Tối ưu hóa (JIT, gõ suy luận, biên dịch)
  • Công cụ triển khai tích hợp
  • Hỗ trợ thư viện
  • Nơi hỗ trợ và thu thập cộng đồng
  • Thư viện tiêu chuẩn phong phú
  • Cú pháp tốt
  • Đọc vòng lặp in eval
  • Hỗ trợ lập trình phản chiếu

3
Haskell đã quá tải. Các lớp loại là tính năng ngôn ngữ được sử dụng để xác định các hàm quá tải.
Lii

2

Câu trả lời tốt! Câu trả lời của A.Rex nói riêng là rất chi tiết và mang tính hướng dẫn. Như ông chỉ ra, C ++ không xem xét khai thác loại chuyển đổi người dùng cung cấp khi biên dịch lhs = func(); (nơi func thực sự là tên của một struct) . Cách giải quyết của tôi hơi khác một chút - không tốt hơn, chỉ khác (mặc dù nó dựa trên cùng một ý tưởng cơ bản).

Trong khi đó tôi đã muốn viết ...

template <typename T> inline T func() { abort(); return T(); }

template <> inline int func()
{ <<special code for int>> }

template <> inline double func()
{ <<special code for double>> }

.. etc, then ..

int x = func(); // ambiguous!
int x = func<int>(); // *also* ambiguous!?  you're just being difficult, g++!

Tôi đã kết thúc với một giải pháp sử dụng cấu trúc được tham số hóa (với T = kiểu trả về):

template <typename T>
struct func
{
    operator T()
    { abort(); return T(); } 
};

// explicit specializations for supported types
// (any code that includes this header can add more!)

template <> inline
func<int>::operator int()
{ <<special code for int>> }

template <> inline
func<double>::operator double()
{ <<special code for double>> }

.. etc, then ..

int x = func<int>(); // this is OK!
double d = func<double>(); // also OK :)

Một lợi ích của giải pháp này là bất kỳ mã nào bao gồm các định nghĩa mẫu này có thể thêm nhiều chuyên môn hơn cho nhiều loại hơn. Ngoài ra, bạn có thể làm chuyên môn một phần của cấu trúc khi cần thiết. Ví dụ: nếu bạn muốn xử lý đặc biệt cho các loại con trỏ:

template <typename T>
struct func<T*>
{
    operator T*()
    { <<special handling for T*>> } 
};

Như một tiêu cực, bạn không thể viết int x = func();với giải pháp của tôi. Bạn phải viết int x = func<int>();. Bạn phải nói rõ ràng kiểu trả về là gì, thay vì yêu cầu trình biên dịch loại bỏ nó bằng cách xem xét các toán tử chuyển đổi loại. Tôi sẽ nói rằng giải pháp "của tôi" và cả A.Rex đều thuộc về một cách tối ưu pareto để giải quyết vấn đề nan giải C ++ này :)


1

nếu bạn muốn nạp chồng các phương thức với các kiểu trả về khác nhau, chỉ cần thêm một tham số giả với giá trị mặc định để cho phép thực thi quá tải, nhưng đừng quên loại tham số nên khác nhau để logic quá tải hoạt động tiếp theo là ví dụ trên delphi:

type    
    myclass = class
    public
      function Funct1(dummy: string = EmptyStr): String; overload;
      function Funct1(dummy: Integer = -1): Integer; overload;
    end;

sử dụng nó như thế này

procedure tester;
var yourobject : myclass;
  iValue: integer;
  sValue: string;
begin
  yourobject:= myclass.create;
  iValue:= yourobject.Funct1(); //this will call the func with integer result
  sValue:= yourobject.Funct1(); //this will call the func with string result
end;

Đó là một ý tưởng tồi tệ. Đừng giới thiệu các thông số giả, đó là một mùi mã lớn. Thay vào đó, chọn các tên khác nhau, hoặc chọn một loại trả về có thể hoạt động như thế, hoặc là một liên minh bị phân biệt đối xử hoặc một cái gì đó.
Abel

@ Tin những gì bạn đang đề xuất thực sự là ý tưởng khủng khiếp, bởi vì toàn bộ ý tưởng là về tham số giả này, và nó được đặt tên như vậy để làm rõ cho nhà phát triển rằng tham số này là giả và nên bỏ qua, trong trường hợp bạn không biết các tham số giả với các giá trị mặc định được sử dụng trong nhiều thư viện, VCL trong delphi và nhiều IDE, ví dụ: trong delphi bạn có thể thấy nó trong đơn vị sysutils trong SafeLoadL Library ...
ZORRO_BLANCO

Chắc chắn có các kịch bản trong đó các tham số giả là hữu ích, như trong lambdas trong các thao tác bản đồ hoặc gấp hoặc khi thực hiện giao diện. Nhưng chỉ vì mục đích tạo ra sự quá tải, không, tôi xin không đồng ý. Không có nhu cầu và đó là tiếng ồn mà các lập trình viên có thể sống mà không có.
Abel

0

Như đã được hiển thị - các lệnh gọi mơ hồ của một hàm chỉ khác nhau bởi kiểu trả về giới thiệu sự mơ hồ. Sự mơ hồ gây ra mã bị lỗi. Mã lỗi phải tránh

Sự phức tạp được thúc đẩy bởi nỗ lực mơ hồ cho thấy đây không phải là một vụ hack tốt. Ngoài một bài tập trí tuệ - tại sao không sử dụng các thủ tục với các tham số tham chiếu.

procedure(reference string){};
procedure(reference int){};
string blah;
procedure(blah)

Bởi vì bạn không thể dễ dàng sử dụng lại các giá trị "trả lại" ngay lập tức. Bạn sẽ phải thực hiện mỗi cuộc gọi trên một đường dây, trái ngược vớidoing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
Adowrath 18/03/2017

0

tính năng quá tải này không khó để quản lý, nếu bạn nhìn nó theo một cách hơi khác. xem xét những điều sau đây

public Integer | String f(int choice){
if(choice==1){
return new string();
}else{
return new Integer();
}}

nếu một ngôn ngữ đã trả lại quá tải, nó sẽ cho phép quá tải tham số, nhưng không trùng lặp. điều này sẽ giải quyết vấn đề:

main (){
f(x)
}

bởi vì chỉ có một f (int sự lựa chọn) để lựa chọn.


0

Trong .NET, đôi khi chúng tôi sử dụng một tham số để chỉ ra đầu ra mong muốn từ kết quả chung và sau đó thực hiện chuyển đổi để có được những gì chúng tôi mong đợi.

C #

public enum FooReturnType{
        IntType,
        StringType,
        WeaType
    }

    class Wea { 
        public override string ToString()
        {
            return "Wea class";
        }
    }

    public static object Foo(FooReturnType type){
        object result = null;
        if (type == FooReturnType.IntType) 
        {
            /*Int related actions*/
            result = 1;
        }
        else if (type == FooReturnType.StringType)
        {
            /*String related actions*/
            result = "Some important text";
        }
        else if (type == FooReturnType.WeaType)
        {
            /*Wea related actions*/
            result = new Wea();
        }
        return result;
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Expecting Int from Foo: " + Foo(FooReturnType.IntType));
        Console.WriteLine("Expecting String from Foo: " + Foo(FooReturnType.StringType));
        Console.WriteLine("Expecting Wea from Foo: " + Foo(FooReturnType.WeaType));
        Console.Read();
    }

Có lẽ ví dụ này cũng có thể giúp:

C ++

    #include <iostream>

enum class FooReturnType{ //Only C++11
    IntType,
    StringType,
    WeaType
}_FooReturnType;

class Wea{
public:
    const char* ToString(){
        return "Wea class";
    }
};

void* Foo(FooReturnType type){
    void* result = 0;
    if (type == FooReturnType::IntType) //Only C++11
    {
        /*Int related actions*/
        result = (void*)1;
    }
    else if (type == FooReturnType::StringType) //Only C++11
    {
        /*String related actions*/
        result = (void*)"Some important text";
    }
    else if (type == FooReturnType::WeaType) //Only C++11
    {
        /*Wea related actions*/
        result = (void*)new Wea();
    }
    return result;
}

int main(int argc, char* argv[])
{
    int intReturn = (int)Foo(FooReturnType::IntType);
    const char* stringReturn = (const char*)Foo(FooReturnType::StringType);
    Wea *someWea = static_cast<Wea*>(Foo(FooReturnType::WeaType));
    std::cout << "Expecting Int from Foo: " << intReturn << std::endl;
    std::cout << "Expecting String from Foo: " << stringReturn << std::endl;
    std::cout << "Expecting Wea from Foo: " << someWea->ToString() << std::endl;
    delete someWea; // Don't leak oil!
    return 0;
}

1
Đây là một loại hackish và có thể dẫn đến lỗi thời gian chạy nếu người dùng không đưa ra kết quả đúng hoặc nếu nhà phát triển không khớp chính xác các loại trả về với enum. Tôi sẽ khuyên bạn nên sử dụng cách tiếp cận dựa trên mẫu (hoặc tham số chung trong C #?), Chẳng hạn như trong câu trả lời này
sleblanc

0

Đối với bản ghi, Octave cho phép kết quả khác nhau tùy theo phần tử trả về là vô hướng so với mảng.

x = min ([1, 3, 0, 2, 0])
   ⇒  x = 0

[x, ix] = min ([1, 3, 0, 2, 0])
   ⇒  x = 0
      ix = 3 (item index)

Cf cũng phân tách giá trị số ít .


0

Cái này hơi khác với C ++; Tôi không biết nếu nó được coi là quá tải bằng cách trả về trực tiếp. Đó là nhiều hơn một chuyên môn mẫu hoạt động theo cách.

tận dụng

#ifndef UTIL_H
#define UTIL_H

#include <string>
#include <sstream>
#include <algorithm>

class util {
public: 
    static int      convertToInt( const std::string& str );
    static unsigned convertToUnsigned( const std::string& str );
    static float    convertToFloat( const std::string& str );
    static double   convertToDouble( const std::string& str );

private:
    util();
    util( const util& c );
    util& operator=( const util& c );

    template<typename T>
    static bool stringToValue( const std::string& str, T* pVal, unsigned numValues );

    template<typename T>
    static T getValue( const std::string& str, std::size_t& remainder );
};

#include "util.inl"

#endif UTIL_H

tận dụng

template<typename T>
static bool util::stringToValue( const std::string& str, T* pValue, unsigned numValues ) {
    int numCommas = std::count(str.begin(), str.end(), ',');
    if (numCommas != numValues - 1) {
        return false;
    }

    std::size_t remainder;
    pValue[0] = getValue<T>(str, remainder);

    if (numValues == 1) {
        if (str.size() != remainder) {
            return false;
        }
    }
    else {
        std::size_t offset = remainder;
        if (str.at(offset) != ',') {
            return false;
        }

        unsigned lastIdx = numValues - 1;
        for (unsigned u = 1; u < numValues; ++u) {
            pValue[u] = getValue<T>(str.substr(++offset), remainder);
            offset += remainder;
            if ((u < lastIdx && str.at(offset) != ',') ||
                (u == lastIdx && offset != str.size()))
            {
                return false;
            }
        }
    }
    return true;    
}

tận dụng

#include "util.h"

template<>
int util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stoi( str, &remainder );
} 

template<>
unsigned util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stoul( str, &remainder );
}

template<>
float util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stof( str, &remainder );
}     

template<>   
double util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stod( str, &remainder );
}

int util::convertToInt( const std::string& str ) {
    int i = 0;
    if ( !stringToValue( str, &i, 1 ) ) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to int";
        throw strStream.str();
    }
    return i;
}

unsigned util::convertToUnsigned( const std::string& str ) {
    unsigned u = 0;
    if ( !stringToValue( str, &u, 1 ) ) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to unsigned";
        throw strStream.str();
    }
    return u;
}     

float util::convertToFloat(const std::string& str) {
    float f = 0;
    if (!stringToValue(str, &f, 1)) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to float";
        throw strStream.str();
    }
    return f;
}

double util::convertToDouble(const std::string& str) {
    float d = 0;
    if (!stringToValue(str, &d, 1)) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to double";
        throw strStream.str();
    }
    return d;
}

Ví dụ này không chính xác sử dụng độ phân giải quá tải hàm theo kiểu trả về, tuy nhiên lớp không đối tượng c ++ này đang sử dụng chuyên môn hóa mẫu để mô phỏng độ phân giải quá tải hàm theo kiểu trả về với phương thức tĩnh riêng.

Mỗi convertToTypehàm đang gọi mẫu hàm stringToValue()và nếu bạn nhìn vào chi tiết triển khai hoặc thuật toán của mẫu hàm này thì nó đang gọi getValue<T>( param, param )và nó sẽ trả về một kiểu Tvà lưu nó vào một kiểu T*được truyền vàostringToValue() mẫu hàm như một trong các tham số của nó .

Khác với những thứ như thế này; C ++ không thực sự có một cơ chế để có chức năng giải quyết quá tải theo kiểu trả về. Có thể có các cấu trúc hoặc cơ chế khác mà tôi không biết có thể mô phỏng độ phân giải theo kiểu trả về.


-1

Tôi nghĩ rằng đây là một GAP trong định nghĩa C ++ hiện đại tại sao?

int func();
double func();

// example 1. → defined
int i = func();

// example 2. → defined
double d = func();

// example 3. → NOT defined. error
void main() 
{
    func();
}

Tại sao trình biên dịch C ++ không thể đưa ra lỗi trong ví dụ "3" và chấp nhận mã trong ví dụ "1 + 2" ??


Vâng, đó là những gì họ đã xem xét tại thời điểm đó cho C # (và có thể là C ++). Nhưng trong khi mã của bạn là tầm thường, một khi bạn thêm hệ thống phân cấp lớp, phương thức ảo, tóm tắt và giao diện, quá tải khác và đôi khi nhiều kế thừa, sẽ rất nhanh để quyết định phương thức nào sẽ được giải quyết. Đó là lựa chọn của các nhà thiết kế không đi theo con đường đó, nhưng các ngôn ngữ khác đã quyết định khác nhau ở các mức độ thành công khác nhau.
Abel

-2

Hầu hết các ngôn ngữ tĩnh hiện nay cũng hỗ trợ thuốc generic, sẽ giải quyết vấn đề của bạn. Như đã nêu trước đây, không có tham số khác, không có cách nào để biết nên gọi cái nào. Vì vậy, nếu bạn muốn làm điều này, chỉ cần sử dụng thuốc generic và gọi nó là một ngày.


Không giống nhau. Làm thế nào bạn sẽ xử lý một chức năng dịch đầu vào thành một số nguyên, float, bool hoặc bất cứ điều gì dựa trên cách sử dụng kiểu trả về? Nó không thể được khái quát vì bạn cần một trường hợp đặc biệt cho mỗi trường hợp.
Jay Conrod

Xem codeproject.com/KB/cpp/returnoverload.aspx để biết chiến lược thông minh về "quá tải cho loại trả về". Về cơ bản, thay vì xác định hàm func (), bạn xác định hàm func, cung cấp cho nó một toán tử () () và chuyển đổi cho từng loại thích hợp.
j_random_hacker

Jay, bạn xác định kiểu trả về khi bạn gọi hàm. Nếu inpus là khác nhau, thì không có vấn đề gì cả. Nếu giống nhau, bạn có thể có một phiên bản chung có thể có một số logic dựa trên loại sử dụng GetType ().
Charles Graham
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.