按返回类型重载

102

我在stackoverflow上看了一些关于这个主题的问题,但这似乎仍然困惑我。我刚开始学习C++,还没有学习模板或运算符重载等内容。

现在有没有简单的方法来进行重载?

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

没有使用模板或奇怪的行为?还是我只需要

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

3
可能是按返回类型进行函数重载?的重复问题。 - user195488
1
@AdamV,我真的很喜欢你的评论。简短而且非常扎实。 - Pouya
@Adam V 实际上,获取重载函数的地址已经存在这样的歧义。在这种情况下,表达式应该有一些类型期望。如果没有,则程序是非法的。这已经得到了实现。我认为使用相同的规则来实现按返回类型进行函数重载不会很难。因此,在您的具体示例中,可以通过返回类型的转换来消除歧义。使用 int 返回值的实例将如下所示:(int)get(9),而使用 char 的实例将如下所示:(char)get(9) - AnArrayOfFunctions
当你到达这里时,我认为最好的选择是像Luchian建议的那样考虑两个不同的函数名称。 - Kemin Zhou
11个回答

121
没有这样的功能。你不能根据返回类型重载方法。
重载解析会考虑函数签名,函数签名由以下组成:
- 函数名称 - cv-限定符 - 参数类型
以下是引用的内容: 1.3.11 签名 “参与重载解析(13.3)的函数信息:其参数类型列表(8.3.5),如果函数是类成员,则还包括该函数本身和声明该成员函数的类上的cv-限定符(如果有)。[...]” 选项: 1)更改方法名称:
class My {
public:
    int getInt(int);
    char getChar(int);
};

2) 输出参数:

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

3) 在这种情况下,使用模板可能过于复杂。


21
在普通函数中,你不能以返回类型作为重载条件,但编译器会根据结果类型来选择转换运算符;你可以利用这点创建一个代理对象,实现对返回类型的重载。 - James Kanze
2
@JeffPigarelli 模板解决方案意味着成员模板:My::get<T>(int)。如果满足以下条件,它是一个有效的选择:1)你需要处理许多不同类型的情况,但基本代码相同(例如 boost::lexical_cast<T>( someStringValue )),或者你需要能够从其他模板中调用这些函数(myMy.get<T>( i ),其中 T 是另一个模板的参数)。否则,正如Luchian所说,它们就是过度设计。 - James Kanze
51
请注意,无法根据返回类型进行重载的原因是因为C++允许您丢弃函数调用的值。因此,如果您仅调用my.get(0),编译器将无法决定执行哪一段代码。 - benzado
11
在这种情况下,它应该仅在这些情况下抛出编译器错误,否则它应该像在许多其他情况下一样推断类型。 - rr-
5
出于相同的原因,void foo(int x = 0) {}void foo(double x = 0) {} 应该被禁止。然而,实际上它们并没有被禁止。只有当编译器无法区分时(foo()),你才会收到错误提示。 - 463035818_is_not_a_number
显示剩余2条评论

91

这是可能的,但我不确定这是否是一种适合初学者的技术。像其他情况一样,当您想要函数的选择取决于返回值的使用方式时,可以使用代理; 首先定义像getChargetInt这样的函数,然后再编写一个通用的get()函数,该函数返回一个代理,例如:

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

将其扩展到您需要的任意类型。


13
+1,虽然这只是一个极端案例,但类型转换运算符实际上是针对返回类型进行重载的,并且可以利用它来在几乎任何地方获得此功能。 - Matthieu M.
4
关于隐式转换,几乎到处都可以使用,但需要注意潜在的歧义。然而,在代理对象的情况下,我认为风险很小——除非你想要进行隐式转换,否则你不会有代理类型的实例。请注意,代理中的转换算作一次用户定义的转换。如果你需要 std::string,而代理只提供了 operator char const*(),那么将无法工作。 - James Kanze
为什么在这里使用代理,我想不出任何必须使用代理的情况。你能提供一个吗?谢谢! - Chen Li
@ChenLi 因为转换运算符是基于返回类型解析函数的唯一方式,这意味着你必须有一个代理来进行转换。 - Mooing Duck

16

如前所述,在这种情况下,模板可能有些过于复杂,但它仍然是值得一提的选项。

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

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

正是我想要的,谢谢 :) - Charley Wright

10
不,你不能通过返回类型进行重载;只能通过参数类型和const / volatile限定符进行重载。一个替代方法是使用引用参数来“返回”:
void get(int, int&);
void get(int, char&);

尽管我可能会使用模板或者像你第二个示例中使用不同名称的函数。


类似于EFI API,返回类型为“int”错误代码。 - Cole Tobin
1
注意,char和int类型可以隐式转换。 - Kemin Zhou
如果使用模板,当你使用它时,可能需要在尖括号中显式提供类型,对吧?因为在许多情况下,编译器无法知道你正在处理的确切类型。 - thomasrutter
嗯,fabda01的答案似乎有解决办法,但我不明白它是如何工作的。 - thomasrutter

9
您可以这样理解:
您拥有:

一台计算机

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

而且,在调用函数时,收集返回值并非强制要求。

现在,你调用

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

因此,如果基于返回类型允许重载,则没有任何意义。

9

重新启动一个旧主题,但我发现没有人提到引用限定符的重载。引用限定符是C++11中添加的一种语言特性,我最近才发现它 - 它不像例如cv-限定符那样广泛。其主要思想是区分两种情况:当成员函数在rvalue对象上调用时和在lvalue对象上调用时。你基本上可以编写类似这样的代码(我稍微修改了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
}

这段代码将产生以下输出:
returning int..
returning char..

当然,与cv限定符一样,这两个函数可以返回相同的类型,并且重载仍然成功。

2
对于这里正在发生的情况,实际上是在隐藏的“this”参数(this& vs this&&)上进行常规函数重载。 - studgeek

3

虽然其他评论在技术上都是正确的,但如果您将返回值重载与输入参数重载相结合,您可以有效地重载它。例如:

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

示例:

#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) );
}

这将导致以下结果:
I 
C

11
完全改变函数签名,因此您不是通过返回类型进行重载,而是简单地进行重载。 - Dado
我成功地使用了这种方法来为运行生产的C++ JSON API返回各种值。效果很好!虽然从技术上讲,它不是通过返回类型进行重载,但它实现了相同函数名具有不同返回类型的意图,是有效且清晰的C++代码,并且开销很小(在函数调用中只有一个变量实例化)。 - guidotex
1
虽然这不是按返回类型重载,但它能胜任工作。让我说“狡猾”。 - Marcus

2

在C++中,没有办法通过返回类型进行重载。如果不使用模板,使用get_intget_char将是你能做的最好的选择。


确认一下:类似 template <class T> T get(int) 这样的写法可行吗? - Niklas B.
4
是的,@Niklas,但你必须将其称为get<int>get<char>,如果你没有使用其他模板特性,这并不比get_intget_char更有优势。 - Rob Kennedy
@Rob:如果你有类似于T get(T)的东西,编译器可以确定T。如果你调用get('a'),编译器推断出T是一个char,你不必显式地调用get<char>('a')。我仍然不确定这是否是标准,尽管我认为它是。FYI,GCC和Clang都支持这个。 - netcoder
1
这是非常标准的,@Netcoder,但这并不是编译器仅推断返回类型的情况,你曾经提到过这是可能的。在你的例子中,编译器推断出参数类型,一旦知道了这个,它就会在其他所有地方填充T的值,包括返回类型。我期望你能给出一个编译器推断Niklas第一条评论中函数的T的例子。 - Rob Kennedy

2

你不能根据返回类型重载方法。最好的方法是创建两个具有略微不同语法的函数,就像你第二段代码片段中那样。


0

我使用了James Kanze的答案,使用了代理:

https://dev59.com/SWkw5IYBdhLWcg3w6Oty#9569120

我想避免在 void* 上使用大量丑陋的 static_cast,所以我做了这个:
#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;
}

运行完美!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接