[C++] Function Overloading By Returntype

Find this example on compiler explorer or on GitHub.

 

Recently I came across the following problem:

int get_some_value(); // ok
int get_some_value(int arg1); // ok
int get_some_value(int arg1, int arg2); // ok
std::string get_some_value(const std::string& s); // ok

int get_some_value(); // ok
std::string get_some_value();   // Error, can't overload by returntype

And this is defined by the C++ language. But we can use a workaround to make this acutally work.

When you define a class you can overload the corresponding operators (+, -, ++, +=, etc.). Also you can write overloads when your class is assigned to some value:

struct return_99 {
    operator int() const {
        return 99;
    }
    operator std::string() const {
        return "99";
    }
};
int i = return_99(); // ok
std::string s = return_99(); // ok

And there we make use of operator overloading. We'll define a class (like this return_99), which has all the needed overloads implemented.

We define some_value. This is the return value of our function get_some_value. The operator overloads are templated and we'll call the concrete implementation in a separate implementation class. To make sure our desired return type is supported (and implemented) we add a static_assert.

This means:

  1. We implement the some_value_impl class
  2. We implement a function to check if we support the conversion to a given type (has_get_some_value_v<T>)
struct some_value {
    // add members as you wish
    template<typename T>
    operator T() const {
        static_assert(has_get_some_value_v<T>, "conversion to T not supported");
        return some_value_impl<T>::get_value();
    }
};
some_value get_some_value(/*insert args here if needed*/) {
    return some_value{/*pass members to some_value*/};
}
 

Implementation For Different Types: some_value_impl

The implementation for the different types are now fairly easy. This means we define two implementations, for integers and strings (I just return some arbitrary values here):

template<typename T>
struct some_value_impl{};

// ... 

template <>
struct some_value_impl<int> {
    static int get_value() {
        return 154;
    }
};

template <>
struct some_value_impl<std::string> {
    static std::string get_value() {
        return "some value ...";
    }
};

Note: We need to define get_value inside each implementation static (see how its called in struct some_value). And thats technically it. We furthermore add some static checks, so if we use not supported types here, our compiler will tell us.

 

Check Supported Types has_some_value_v

To check if we support a given type or not, we define the templated class has_some_value_function, with a static member value. This member will evaluate if we support a given type. The template variable has_some_value_v is then just a wrapper to make it's access easier.

template<typename T>
class has_some_value_function {
    using one = char;
    struct two {
        char x[2];
    };

    template<typename C>
    static one test(decltype(&C::get_value));
    template<typename C>
    static two test(...);

public:
    static constexpr bool value = sizeof(test<T>(0)) == sizeof(char);
};

template<typename T>
static constexpr bool has_some_value_v = has_some_value_function<some_value_impl<T>>::value;

As short explanation for this class, the value is assigned to the overload of test, which depends if the function C::get_value (which is the called function in our implementation class) exists or not.

However, we are finished with implementing the overload by return type. We can now assign different values to the same function and get different types back:

int i = get_some_value(); // returns: 154
std::string s = get_some_value(); // returns some value ...

Find this example on compiler explorer or on GitHub

 

Conclusion

Currently I'm working on customized Lua bindings. Here I want to access different values from Lua by the square brackets operator:

int i = my_lua_class["integer_value"]
std::string s = my_lua_class["string_value"]

And now I need to distinguish wether I'm accessing an integer or a string. Of course there are other solutions, like a templated access function which could look like this:

int i = my_lua_class.get_as<int>("integer_value")
std::string s = my_lua_class.get_as<std::string>("string_value")

But I preferred this solution by overloading the returntype because it makes the access to it more idiomatic. But you have the limitation that you can't use auto, which you don't have with get_as<T>(..).

Similar use cases you could have on accessing xml or json files. You have a given tag and you want a value back, but what type do want? And their this example comes into place.

Anyway, I hope that was helpful.

Best Thomas.

 

Find this example on compiler explorer or on GitHub.

Previous
Previous

[C++] Tag Dispatching To Overload Functions

Next
Next

[2D Game Engine] Starting A New Project - The Project Setup