Return type overloading

No return type overloading

In C++, return type does not participate in function overload resolution, i.e. it is not possible to overload a function on the return type. Thus, this is not legal C++:

void to_lower( std::string& strg );
std::string to_lower( std::string& strg );

The compiler will issue an error when it sees the second declaration.1 For instance, the error Clang emits is the following: "error: functions that differ only in their return type cannot be overloaded". The detailed reasons for this rule are irrelevant in the current discussion: it is just a fact. I believe it is partially due to C++ allowing you to ignore the return value of a function if you so choose. Thus, even for a function returning an int, you can call it without using or even capturing the return value:

int fn( int );

auto ret_val = fn( 5 );
fn( 12 ); // legal call, simply drops the return value

In the example, if fn were overloaded on return type only, how would the second call be disambiguated? I am far from certain this is the only reason why, but it is convincing enough for me.

Still, I really wanted to be able to have an "in place" and "not in place" version of the to_lower function I wrote. And, naming things is hard, so I did not want to have to change the name of the function. Cheating a little bit, I have found a way to simulate function overloading on return type or at least make it possible to have the same name for the two functions and (almost) only a differing return type.

With what I came up with, one of the two overloads becomes a template and must be called explicitly, so no ADL or overload resolution alone can help me and template argument deduction cannot be used either because the function parameter(s) do not depend on the template parameter. After all, it is illegal to overload on return type in C++ and my technique does not imply changing the ISO C++ standard!

How I cheated

All I did is use a template parameter as a tag for one version of the function. I created an empty struct as a tag and templated one of the versions (the one the tag corresponds to). Then, when I want that version to be called, I explicitly select the templated version passing the tag as an explicitly specified template argument. Here is a code sample to illustrate/explain better:

#include <type_traits>
#include <string>
#include <cstdio>
namespace ns
{
struct in_place {};

template< typename Tag >
void to_lower( std::string& strg )
{
   static_assert( std::is_same< Tag, in_place >::value,
                     "only in_place is allowed as the template parameter" );
   //... implementation here ...
}

std::string to_lower( std::string& strg )
{
   //... implementation here ...
   return strg;
}

} // namespace ns

int main( int argc, char* argv[] )
{
   using ns::in_place;
   using ns::to_lower;

   std::string to_change( "This STring is Mixed CASe." );

   auto new_strg = to_lower( to_change );
   to_lower< in_place >( to_change );

   printf( "%s\n", ( to_change == new_strg ? "true" : "false" ) );
   return 0;
}

Users of the function are provided with the tag and can use it to call the "overload" (so to speak, not an actual overload, I know). Compilers are actually pretty good at eliminating empty classes from the code, even at not too high optimization levels. So I am confident that the tag should disappear completely from the resulting binary and there should be no runtime downside to this technique.2 Also, if appropriate, one function can be implemented using the other. To ensure that types other than the provided tag can't be used for the function call, I put a static_assert in the implementation. Otherwise, any type could be put in there (as in to_lower<int>), which although it would work, would not be as explicit and as clear. I don't know why anyone would want do that, but I forbid it explicitly in my implementation.

What I did is no revolution and nothing fancy, but it does allow me to get the interface I need from my function, namely two functions with the same name differing only in return types... or almost. I also find it to be expressive: it is clear at the call site that I will be modifying the string that is passed in. Some might like it, others won't! If you find it useful, good.

Alternative

A friend suggested that another possible implementation would be to use variadic templates and call the function with an empty diamond. I tried that version and it is possible. I even put in a static_assert to check that the function can't be called with a non empty parameter pack. That being said, I find that it is better to have the empty struct as a tag because it is a bit more explicit which overload actually acts in place (whereas in the empty parameter pack alternative, it is clear which overload is called (the template), but not whether this is the one working in place). I suspect either implementation would result in the same binary code. To me, another argument in favor of keeping the tag is that tag dispatching (which my technique is similar to) is familiar to many C++ programmers while seeing a call with the empty diamond is not so common (I think, I might be completely wrong).

Notes

[1] I know that the second overload, the one returning a new string, could take it's parameter by reference or by value (since I'm probably doing a copy in the implementation anyhow). That would have complicated the explanation of the overloads (as in that case, both functions would not technically be exactly differing only in return type), so I chose to write my examples that way in the post. ↩︎

[2] I would have "Godbolted" my code, but I am not that good at reading assembler and when I tried with the std::string, the resulting assembler contains much more than my function and is not that simple to analyse. I did it with ints, but then, starting at -O2, main becomes almost empty as the compiler can see all the constants and simplify all the way. ↩︎