Quá tải theo loại trả lại


80

Tôi đọc được một số câu hỏi ở đây trên SO về chủ đề này mà dường như vẫn còn khó hiểu đối với tôi. Tôi mới bắt đầu học C ++ và tôi chưa nghiên cứu các khuôn mẫu hoặc nạp chồng toán tử và những thứ tương tự.

Bây giờ có một cách đơn giản để quá tải

class My {
public:
    int get(int);
    char get(int);
}

không có khuôn mẫu hoặc hành vi lạ? hay tôi chỉ nên

class My {
public:
    int get_int(int);
    char get_char(int);
}

?


3
có thể trùng lặp của Nạp chồng hàm theo kiểu trả về?

1
@AdamV, tôi thực sự thích nhận xét của bạn. Ngắn gọn nhưng hoàn toàn chắc chắn.
Pouya

@Adam V Trên thực tế đã có sự mơ hồ như vậy với việc lấy địa chỉ của một hàm quá tải. Trong trường hợp này, cần có một số kiểu kỳ vọng của biểu thức. Trong trường hợp không có một chương trình là sai. Và điều này đã được thực hiện. Tôi không nghĩ sẽ rất khó để sử dụng các quy tắc tương tự để thực hiện nạp chồng hàm theo kiểu trả về. Vì vậy, trong ví dụ cụ thể của bạn, sự mơ hồ sẽ được loại bỏ với một kiểu được trả về. Lập tức với intgiá trị trả về sẽ giống như thế này (int)get(9)chartương tự như thế này (char)get(9).
AnArrayOfFunctions

Khi bạn đến đây, tôi nghĩ lựa chọn tốt nhất là nghĩ về hai tên chức năng khác nhau như Luchian đề xuất.
Kemin Zhou

Câu trả lời:


100

Không, không có. Bạn không thể nạp chồng các phương thức dựa trên kiểu trả về.

Giải quyết quá tải có tính đến chữ ký chức năng . Chữ ký hàm được tạo thành từ:

  • Tên chức năng
  • vòng loại cv
  • các loại tham số

Và đây là câu trích dẫn:

1.3.11 chữ ký

thông tin về một hàm tham gia phân giải quá tải (13.3): tham số-kiểu-danh sách (8.3.5) của nó và, nếu hàm là một thành viên lớp, thì các vòng loại cv (nếu có) trên chính hàm và lớp trong đó hàm thành viên được khai báo. [...]

Các tùy chọn:

1) thay đổi tên phương thức:

class My {
public:
    int getInt(int);
    char getChar(int);
};

2) tham số ra:

class My {
public:
    void get(int, int&);
    void get(int, char&);
}

3) mẫu ... quá mức cần thiết trong trường hợp này.


17
Bạn không thể quá tải một hàm bình thường trên kiểu trả về, nhưng trình biên dịch sẽ chọn giữa các toán tử chuyển đổi dựa trên kiểu kết quả; bạn có thể tận dụng điều này để tạo một proxy hoạt động hiệu quả như thể bạn đã quá tải trên loại trả lại.
James Kanze

2
@JeffPigarelli Các phương tiện mẫu giải pháp mẫu thành viên: My::get<T>(int). Đó là một giải pháp thay thế hợp lệ _ nếu 1) bạn phải xử lý nhiều kiểu khác nhau, tất cả đều có cùng một mã cơ bản (ví dụ: boost::lexical_cast<T>( someStringValue )hoặc bạn phải có thể gọi các hàm này từ một số mẫu khác ( myMy.get<T>( i ), Tđối số của mẫu khác này ở đâu) . Nếu không, như Luchian nói, chúng đang ở mức quá mức cần thiết.
James Kanze

39
Lưu ý rằng lý do bạn không thể quá tải dựa trên kiểu trả về là C ++ cho phép bạn loại bỏ giá trị của một lệnh gọi hàm. Vì vậy, nếu bạn chỉ đơn giản gọi là my.get(0);trình biên dịch sẽ không có cách nào để quyết định đoạn mã nào sẽ thực thi.
benzado

9
@benzado trong trường hợp đó, nó chỉ nên ném ra lỗi trình biên dịch trong những trường hợp như vậy, nếu không, nó sẽ suy ra kiểu giống như nó xảy ra trong rất nhiều trường hợp khác.
rr-

2
@benzado vì lý do tương tự void foo(int x = 0) {} void foo(double x = 0) {}nên không được phép. Tuy nhiên, không phải vậy. Chỉ trong trường hợp trình biên dịch thực sự không thể phân biệt được ( foo()), bạn sẽ gặp lỗi
large_prime_is_463035818

82

Nó có thể, nhưng tôi không chắc rằng đó là một kỹ thuật tôi muốn giới thiệu cho người mới bắt đầu. Như trong các trường hợp khác, khi bạn muốn lựa chọn các hàm phụ thuộc vào cách sử dụng giá trị trả về, bạn sử dụng proxy; đầu tiên xác định các hàm như getChargetInt, sau đó là một hàm chung get()trả về một Proxy như sau:

class Proxy
{
    My const* myOwner;
public:
    Proxy( My const* owner ) : myOwner( owner ) {}
    operator int() const
    {
        return myOwner->getInt();
    }
    operator char() const
    {
        return myOwner->getChar();
    }
};

Mở rộng nó thành nhiều loại nếu bạn cần.


10
+1, mặc dù là trường hợp góc, toán tử chuyển đổi thực sự bị quá tải đối với loại trả lại và có thể được tận dụng để đưa tính năng này về mọi nơi.
Matthieu M.

3
@MatthieuM. Về mọi nơi, nhưng với những cảnh báo thông thường liên quan đến chuyển đổi ngầm. Bạn có nguy cơ đưa ra những điều mơ hồ mà nếu không sẽ không có. Tuy nhiên, trong trường hợp của Proxy, tôi nghĩ rủi ro là nhỏ --- bạn sẽ không có các trường hợp của loại Proxy khác với trường hợp bạn muốn chuyển đổi ngầm. Cũng xin lưu ý rằng chuyển đổi trong proxy được tính là một chuyển đổi do người dùng xác định. Nếu bạn cần std::stringvà proxy chỉ cung cấp operator char const*(), nó sẽ không hoạt động.
James Kanze

Tại sao lại sử dụng proxy ở đây, tôi không thể nghĩ ra bất kỳ trường hợp nào mà proxy là bắt buộc. Bạn có thể cung cấp một? cảm ơn!
陳 力

8

Không, bạn không thể nạp chồng theo kiểu trả về; chỉ bởi các loại tham số và định tính const / biến động.

Một thay thế sẽ là "return" bằng cách sử dụng đối số tham chiếu:

void get(int, int&);
void get(int, char&);

mặc dù tôi có thể sẽ sử dụng một mẫu hoặc các hàm được đặt tên khác như ví dụ thứ hai của bạn.


a la EFI API trong đó loại trả về là intmã lỗi.
Cole Johnson

1
Hãy cẩn thận, các kiểu char và int có thể được chuyển đổi ngầm định.
Kemin Zhou

5

Bạn có thể nghĩ theo cách này:

Bạn có:

  int get(int);
  char get(int);

Và, không bắt buộc phải thu thập giá trị trả về của hàm khi đang gọi.

Bây giờ, bạn gọi

  get(10);  -> there is an ambiguity here which function to invoke. 

Vì vậy, Không có ý nghĩa nếu cho phép quá tải dựa trên kiểu trả về.


4

Đang phục hồi một chuỗi cũ, nhưng tôi có thể thấy rằng không ai đề cập đến việc quá tải bởi các bộ định lượng ref. Vòng loại tham chiếu là một tính năng ngôn ngữ được thêm vào trong C ++ 11 và tôi chỉ mới tình cờ thấy nó gần đây - nó không phổ biến như vòng loại cv. Ý tưởng chính là để phân biệt giữa hai trường hợp: khi hàm thành viên được gọi trên một đối tượng rvalue và khi nào được gọi trên một đối tượng lvalue. Về cơ bản bạn có thể viết một cái gì đó như thế này (tôi đang sửa đổi một chút mã của OP):

#include <stdio.h>

class My {
public:
    int get(int) & { // notice &
        printf("returning int..\n");
        return 42;
    }
    char get(int) && { // notice &&
        printf("returning char..\n");
        return 'x';
    };
};

int main() {
    My oh_my;
    oh_my.get(13); // 'oh_my' is an lvalue
    My().get(13); // 'My()' is a temporary, i.e. an rvalue
}

Mã này sẽ tạo ra kết quả sau:

returning int..
returning char..

Tất nhiên, như trường hợp của các định nghĩa cv, cả hai hàm có thể trả về cùng một kiểu và việc nạp chồng vẫn sẽ thành công.


4

Như đã nêu trước đây, các mẫu là quá mức cần thiết trong trường hợp này, nhưng nó vẫn là một lựa chọn đáng được đề cập.

class My {
public:
    template<typename T> T get(int);
};

template<> int My::get<int>(int);
template<> char My::get<char>(int);

3

Trong khi hầu hết các nhận xét khác về vấn đề này là đúng về mặt kỹ thuật, bạn có thể quá tải giá trị trả về một cách hiệu quả nếu bạn kết hợp nó với quá tải tham số đầu vào. Ví dụ:

class My {
public:
    int  get(int);
    char get(unsigned int);
};

BẢN GIỚI THIỆU:

#include <stdio.h>

class My {
public:
    int  get(         int x) { return 'I';  };
    char get(unsinged int x) { return 'C';  };
};

int main() {

    int i;
    My test;

    printf( "%c\n", test.get(               i) );
    printf( "%c\n", test.get((unsigned int) i) );
}

Kết quả của việc này là:

I 
C

6
mà hoàn toàn thay đổi chữ ký chức năng để bạn không phải quá tải bởi kiểu trả về, bạn chỉ đơn giản là quá tải
Dado

Tôi đã sử dụng thành công phương pháp này để trả về nhiều giá trị khác nhau cho API C ++ JSON đang chạy sản xuất. Làm việc rất tốt! Mặc dù về mặt kỹ thuật không nạp chồng theo kiểu trả về, nhưng nó thực hiện mục đích của các kiểu trả về khác nhau có cùng tên hàm, là C ++ hợp lệ và rõ ràng, và có ít chi phí (một biến duy nhất trong lệnh gọi hàm).
Guidotex

Mặc dù đây không phải là quá tải bởi kiểu trả về, nhưng nó thực hiện công việc. khiến tôi nói "lén lút"
Marcus

2

Không có cách nào để nạp chồng bằng kiểu trả về trong C ++. Nếu không sử dụng các mẫu, sử dụng get_intget_charsẽ là điều tốt nhất bạn có thể làm.


Chỉ để chắc chắn: Một cái gì đó giống như template <class T> T get(int)sẽ hoạt động?
Niklas B.

4
Đúng, @Niklas, nhưng bạn phải gọi nó là get<int>hoặc get<char>, điều này không thực sự giúp bạn hiểu được nhiều hơn get_intget_charnếu bạn cũng không sử dụng các tính năng mẫu khác.
Rob Kennedy

@Rob: Chà, trình biên dịch có thể xác định Txem bạn có thứ gì như thế không T get(T). Nếu bạn gọi get('a'), trình biên dịch sẽ suy ra đó Tlà a charvà bạn không cần phải gọi một cách rõ ràng get<char>('a'). Tôi vẫn không chắc liệu điều này có phải là tiêu chuẩn hay không, mặc dù tôi nghĩ là như vậy. FYI, cả GCC và Clang đều ủng hộ điều này.
netcoder

1
Đó là tiêu chuẩn hoàn toàn, @Netcoder, nhưng nó không phải là trường hợp trình biên dịch chỉ suy luận kiểu trả về, mà bạn đã đề xuất là có thể. Trong ví dụ của bạn, trình biên dịch suy ra kiểu đối số và khi biết điều đó, nó sẽ điền giá trị của Tmọi nơi khác, kể cả kiểu trả về. Tôi mong bạn đưa ra một ví dụ về trình biên dịch suy luận Tcho hàm trong nhận xét đầu tiên của Niklas.
Rob Kennedy

2

Bạn không thể nạp chồng các phương thức dựa trên kiểu trả về. Đặt cược tốt nhất của bạn là tạo hai hàm với cú pháp hơi khác nhau, chẳng hạn như trong đoạn mã thứ hai của bạn.


0

bạn không thể quá tải một hàm dựa trên kiểu trả về của hàm. bạn có thể bổ sung thừa dựa trên kiểu và số lượng đối số mà hàm này sử dụng.


0

Tôi đã sử dụng câu trả lời của James Kanze bằng proxy:

https://stackoverflow.com/a/9569120/262458

Tôi muốn tránh sử dụng nhiều static_cast xấu xí trên một khoảng trống *, vì vậy tôi đã làm điều này:

#include <SDL_joystick.h>
#include <SDL_gamecontroller.h>

struct JoyDev {
    private:
        union {
            SDL_GameController* dev_gc = nullptr;
            SDL_Joystick*       dev_js;
        };
    public:
        operator SDL_GameController*&() { return dev_gc; }
        operator SDL_Joystick*&()       { return dev_js; }

        SDL_GameController*& operator=(SDL_GameController* p) { dev_gc = p; return dev_gc; }
        SDL_Joystick*&       operator=(SDL_Joystick* p)       { dev_js = p; return dev_js; }
};

struct JoyState {
    public:
        JoyDev dev;
};

int main(int argc, char** argv)
{
    JoyState js;

    js.dev = SDL_JoystickOpen(0);

    js.dev = SDL_GameControllerOpen(0);

    SDL_GameControllerRumble(js.dev, 0xFFFF, 0xFFFF, 300);

    return 0;
}

Hoạt động hoàn hảo!

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.