Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Function overloading

87 views
Skip to first unread message

Cholo Lennon

unread,
Jul 20, 2022, 5:42:58 PM7/20/22
to
I am learning Rust. One missing C++/Java feature is function
overloading. One of the explanations of this decision is that "function
overloading is an anti-pattern", because, among other things, "it leads
to less readable and understandable code".

This surprised me because I consider it very useful. I really hate
having to code multiple functions with different names and almost the
same functionality. I suffered this in Python (its solution based in
decorators is not the best), and also in Typescript (its solution based
in several prototypes for a single function is really awful).

What is your opinion about the function overloading as an
"anti-pattern"? is it?

Disclaimer: "Overloading" where only types differ (but the number of
parameters are the same) is possible in Rust using generic traits,
something like the following:

struct MyPrinter;

trait Printer<T> {
fn print(&self, value: T);
}

impl Printer<i32> for MyPrinter {
fn print(&self, value: i32) {
println!("Value: {}", value);
}
}

impl Printer<&str> for MyPrinter {
fn print(&self, value: &str) {
println!("Value: {}", value);
}
}

fn main() {
let foo = MyPrinter;

foo.print(10);
foo.print("Hello");
}


--
Cholo Lennon
Bs.As.
ARG

Lynn McGuire

unread,
Jul 20, 2022, 8:23:07 PM7/20/22
to
I use function overloading extensively in my C++ code code. Works
great and it is useful to have the same name with different arguments in
many classes. Here is one of my helper functions, tuple:

std::vector <int> tuple ();
std::vector <int> tuple (int int1);
std::vector <int> tuple (int int1, int int2);
std::vector <int> tuple (int int1, int int2, int int3);
std::vector <int> tuple (int int1, int int2, int int3, int int4);
std::vector <int> tuple (int int1, int int2, int int3, int int4, int int5);
std::vector <int> tuple (int int1, int int2, int int3, int int4, int
int5, int int6);
std::vector <int> tuple (int int1, int int2, int int3, int int4, int
int5, int int6, int int7);
std::vector <int> tuple (int int1, int int2, int int3, int int4, int
int5, int int6, int int7, int int8);
std::vector <int> tuple (int int1, int int2, int int3, int int4, int
int5, int int6, int int7, int int8, int int9);
std::vector <int> tuple (int int1, int int2, int int3, int int4, int
int5, int int6, int int7, int int8, int int9, int int10);

and so on to int60. I also have tupleString and tupleTuple.

I also have the following methods in my DesValue class:

virtual void setValue (int aValue);
virtual void setValue (double aValue);
virtual void setValue (char * aValue);
virtual void setValue (std::string &aValue);
virtual void setValue (std::vector <int> aValue);
virtual void setValue (std::vector <double> aValue);
virtual void setValue (std::vector <std::string> aValue);
virtual void setString ( std::string aString );
virtual void setString ( std::vector <std::string> aString );

Lynn


Gawr Gura

unread,
Jul 21, 2022, 1:45:14 AM7/21/22
to
Is there a specific reason you've chosen to implement your tuple
function out to 60 elements rather than using a std::vector list
initializer?

Juha Nieminen

unread,
Jul 21, 2022, 1:46:04 AM7/21/22
to
Cholo Lennon <cholo...@hotmail.com> wrote:
> I am learning Rust. One missing C++/Java feature is function
> overloading. One of the explanations of this decision is that "function
> overloading is an anti-pattern", because, among other things, "it leads
> to less readable and understandable code".

Any language feature can be misused and "lead to less readable and
understandable code". Heck, *function names* can be very easily used to
write incomprehensible code (and believe me I have experience on that,
having had to read a boatload of code made by other people... code that
uses *extremely* poor function and variable name choices. That alone
makes a lot of code ten times harder to understand than it would have to.)

I assume that Rust doesn't have support for generic code, then? Because
function overloading is what makes generic code work.

I suppose generic code could still be written without function overloading,
but it would be extraordinarily limited. Heck, the arithmetic and comparison
operators supported by the vast majority of languages are "overloaded
functions" of sort (and are part of what makes generic code work). I assume
Rust has different arithmetic types, and operators like + and - work on
all of them, and you don't have to specify different function names for
different types in order to eg. add them or subtract them.

User-defined overloaded functions aren't much different from overloaded
operators. It's a bit artificial to deliberately limit the overloading of
functions when the language already has overloading of operators built in.

How do you print things in Rust, if it doesn't support function overloading?
Or does it make an exception for itself, for its own elementary functions?

Paavo Helde

unread,
Jul 21, 2022, 2:04:25 AM7/21/22
to
21.07.2022 00:42 Cholo Lennon kirjutas:
> I am learning Rust. One missing C++/Java feature is function
> overloading. One of the explanations of this decision is that "function
> overloading is an anti-pattern", because, among other things, "it leads
> to less readable and understandable code".
>
> This surprised me because I consider it very useful. I really hate
> having to code multiple functions with different names and almost the
> same functionality. I suffered this in Python (its solution based in
> decorators is not the best), and also in Typescript (its solution based
> in several prototypes for a single function is really awful).
>
> What is your opinion about the function overloading as an
> "anti-pattern"? is it?

Function overloading is great for simple utility functions where one
immediately understands what it is expected to do, and it basically does
the same thing in all overloads.

Function overloading becomes anti-pattern when the overloads do
different things. For example, there are processing stages where a
function processes some parameters, then calls another function with the
same name and partially processed parameters. This may create confusion,
especially if I search the codebase for the function name and it is not
immediately obvious which overload is called where. Better to add
suffixes to the names like "stage1" and "stage2" if one cannot think of
better names.

If the functions are not related at all and have the same name just "by
coincidence", then it surprisingly becomes easier because then usually
it is obvious at the call site which overload is used.

Yes I know there is an IDE command "go to definition" and sometimes this
is even working. Still, the less confusion, the better.

Juha Nieminen

unread,
Jul 21, 2022, 3:16:39 AM7/21/22
to
Paavo Helde <ees...@osa.pri.ee> wrote:
> Function overloading is great for simple utility functions where one
> immediately understands what it is expected to do, and it basically does
> the same thing in all overloads.
>
> Function overloading becomes anti-pattern when the overloads do
> different things. For example, there are processing stages where a
> function processes some parameters, then calls another function with the
> same name and partially processed parameters. This may create confusion,
> especially if I search the codebase for the function name and it is not
> immediately obvious which overload is called where. Better to add
> suffixes to the names like "stage1" and "stage2" if one cannot think of
> better names.

I have to deal in the past with production code bases where function
overloading was heavily abused (in more or less a manner of "I'm going
to overload this function name because I can", rather than it making sense),
and it made the code that used those overloaded functions extraordinarily
confusing to read.

For example, the code would be full of function calls of the form
"convert(a, b);" and that's it. Not very informative, plus there was like
nine billion overloaded versions of that function for different types of
first and second parameter, doing almost every possible thing under the
sun (eg. converting from an int, unsigned, long, unsigned long, float,
double, etc. to a std::string or to a std::wstring, with the corresponding
overloaded versions doing the conversion in the other direction. And that
was just one set of conversions. There were many more, including conversions
between string encodings. All named "convert()", with different types
and amounts of parameters.)

In fact, the very fact that the function name didn't even tell if it
was converting from the first parameter to the second or the other
way around made the code really obfuscated to read.

And the thing was, absolutely nowhere in the code was there any advantage
in the function being overloaded. In other words, at no point in the
codebase was there any sort of template or abstracted type that would
have benefited from that 'convert()' function being overloaded.

If I were to refactor all that code, I would create unique names for
all those functions, clearly stating the types and the direction of
conversion, like:

convert_to_int_from_charp(a, b);
convert_to_int_from_string(a, b);
convert_to_string_from_int(a, b);

and so on. If, and only if, the need appears for an overloaded version
of some of those functions, then they can be implemented *in addition*
to those more explicit names (the overloaded versions just calling the
more explicitly named versions), for example like:

void convert_to_value_from_string(int, const std::string&);
void convert_to_value_from_string(double, const std::string&);

etc. Only in the absolute extreme need would I resort to a completely
overloaded name that encompasses all those conversions. Even then I would
still use a name clearer than just 'convert()', even if it's just
something like 'convert_to_value_from_value()' (to indicate which
parameter is the destination and which is the source.)

(And no, I most definitely do not subscribe to the "brevity over clarity"
principle of programming. The more code written by other people I have
to deal with, the less I subscribe to the principle. When I have to read
other people's code, I really get an appreciation for clearly named
functions and variables, which are not only clearly named but also
follow a clear consistent naming pattern and convention, rather than
different patterns and conventions being wildly mixed, even within
the same source file.)

That being said (and as I mentioned in my other reply to this thread),
function overloading most definitely has its use, especially in
generic code.

Lynn McGuire

unread,
Jul 21, 2022, 5:18:10 PM7/21/22
to
I came to C++ from Smalltalk. Tuple was a standard class and function
and was easy to convert using my handwritten converter.

Lynn

Lynn McGuire

unread,
Jul 21, 2022, 5:22:40 PM7/21/22
to
Another one of my favorites are my asString methods.

std::string asString ();
std::string asString (void * val);
std::string asString (int val);
std::string asString (long val);
std::string asString (unsigned int val);
std::string asString (double val);
std::string asString (unsigned long val);
std::string asString (const char *val);
std::string asString (int val, const char * conversion);
std::string asString (long val, const char * conversion);
std::string asString (unsigned int val, const char * conversion);
std::string asString (unsigned long val, const char * conversion);
std::string asString (double val, const char * conversion);
std::string asString (long long val);

Lynn


Cholo Lennon

unread,
Jul 22, 2022, 6:43:18 PM7/22/22
to
On 7/20/22 9:22 PM, Lynn McGuire wrote:
> I use function overloading extensively in my C++ code code.   Works
> great and it is useful to have the same name with different arguments in
> many classes.  Here is one of my helper functions, tuple:
>
> std::vector <int> tuple ();
> std::vector <int> tuple (int int1);
> std::vector <int> tuple (int int1, int int2);
> std::vector <int> tuple (int int1, int int2, int int3);
> std::vector <int> tuple (int int1, int int2, int int3, int int4);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int int5);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int
> int5, int int6);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int
> int5, int int6, int int7);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int
> int5, int int6, int int7, int int8);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int
> int5, int int6, int int7, int int8, int int9);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int
> int5, int int6, int int7, int int8, int int9, int int10);
>
> and so on to int60.  I also have tupleString and tupleTuple.

I suppose your code was written before C++11. Nowadays I would write
something like this:

template<typename T>
auto tuple() -> std::vector<T> {
return std::vector<T>{};
}

template<typename T, typename... Args>
auto tuple(const T& first, const Args&... args) -> std::vector<T> {
return std::vector<T>{ first, args... };
}

Or just use std::vector list initializer as suggested by Gawr Gura.

Lynn McGuire

unread,
Jul 22, 2022, 7:39:14 PM7/22/22
to
My tuple functions were written in 2002 in my port from Smalltalk to
C++. Did std::vector have an initializer back then ? My Smalltalk
conpiler had a heterogeneous collection library using the tuple method
so I wanted a straight conversion between the two. Luckily, most of my
Smalltalk tuples were actually homogeneous collections.

Lynn


Cholo Lennon

unread,
Jul 24, 2022, 12:24:32 AM7/24/22
to
Why the rhetoric question? (both know that std::vector hadn't that in
2002). I explicitly said "I suppose your code was written before C++11.
Nowadays I would write...".

Lynn McGuire

unread,
Jul 25, 2022, 5:02:15 PM7/25/22
to
Sorry, I do not remember when new features came into languages. I have
written software in ten computer languages now. I am approaching a
million lines of code now, it is hard to remember details.

Lynn

Alf P. Steinbach

unread,
Jul 25, 2022, 6:06:10 PM7/25/22
to
On 25 Jul 2022 23:01, Lynn McGuire wrote:
>
> Sorry, I do not remember when new features came into languages.  I have
> written software in ten computer languages now.  I am approaching a
> million lines of code now, it is hard to remember details.

A nice summary of which features were introduced when, for C++.

With discussion of the features.

<url: https://github.com/AnthonyCalandra/modern-cpp-features>

- Alf

Bonita Montero

unread,
Jul 27, 2022, 10:09:08 AM7/27/22
to
Am 21.07.2022 um 02:22 schrieb Lynn McGuire:

> std::vector <int> tuple ();
> std::vector <int> tuple (int int1);
> std::vector <int> tuple (int int1, int int2);
> std::vector <int> tuple (int int1, int int2, int int3);
> std::vector <int> tuple (int int1, int int2, int int3, int int4);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int int5);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int
> int5, int int6);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int
> int5, int int6, int int7);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int
> int5, int int6, int int7, int int8);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int
> int5, int int6, int int7, int int8, int int9);
> std::vector <int> tuple (int int1, int int2, int int3, int int4, int
> int5, int int6, int int7, int int8, int int9, int int10);

You can have that easier:

template<typename To, typename ... Ts>
vector<To> myTuple( Ts &&... params )
requires (convertible_to<Ts, To> && ...)
{
vector<To> vt;
vt.reserve( sizeof ...(Ts) );
auto make_row = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
((Indices, vt.emplace_back( params )), ...);
};
make_row( make_index_sequence<sizeof ...(Ts)>() );
return vt;
}

Works with an arbitrary number of parameters.

Bonita Montero

unread,
Jul 27, 2022, 10:10:27 AM7/27/22
to
Am 27.07.2022 um 16:10 schrieb Bonita Montero:

> template<typename To, typename ... Ts>
> vector<To> myTuple( Ts &&... params )
>     requires (convertible_to<Ts, To> && ...)
> {
>     vector<To> vt;
>     vt.reserve( sizeof ...(Ts) );
>     auto make_row = [&]<size_t ... Indices>( index_sequence<Indices ...> )
>     {
>         ((Indices, vt.emplace_back( params )), ...);
>     };
>     make_row( make_index_sequence<sizeof ...(Ts)>() );
>     return vt;
> }

Now it's complete, I fogot to forward:

template<typename To, typename ... Ts>
vector<To> myTuple( Ts &&... params )
requires (convertible_to<Ts, To> && ...)
{
vector<To> vt;
vt.reserve( sizeof ...(Ts) );
auto make_row = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
((Indices, vt.emplace_back( forward<Ts>( params ) )), ...);

Bonita Montero

unread,
Jul 27, 2022, 10:35:02 AM7/27/22
to
Am 27.07.2022 um 16:10 schrieb Bonita Montero:

> You can have that easier:
>
> template<typename To, typename ... Ts>
> vector<To> myTuple( Ts &&... params )
>     requires (convertible_to<Ts, To> && ...)
> {
>     vector<To> vt;
>     vt.reserve( sizeof ...(Ts) );
>     auto make_row = [&]<size_t ... Indices>( index_sequence<Indices ...> )
>     {
>         ((Indices, vt.emplace_back( params )), ...);
>     };
>     make_row( make_index_sequence<sizeof ...(Ts)>() );
>     return vt;
> }
>
> Works with an arbitrary number of parameters.

This is even more convenient:

template<typename ... Ts>
requires (sizeof ...(Ts) >= 1) && (convertible_to<Ts,
tuple_element_t<0, tuple<Ts ...>>> && ...)
vector<tuple_element_t<0, tuple<Ts ...>>> myTuple( Ts &&... params )
{
vector<tuple_element_t<0, tuple<Ts ...>>> vt;
vt.reserve( sizeof ...(Ts) );
auto make_row = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
((Indices, vt.emplace_back( forward<Ts>( params ) )), ...);

Lynn McGuire

unread,
Jul 27, 2022, 3:56:01 PM7/27/22
to
Sorry, I do not allow templates in our software. We have found out the
hard way that templates need to be written with many problems in mind.

Lynn

Lynn McGuire

unread,
Jul 27, 2022, 4:01:28 PM7/27/22
to
On 7/27/2022 9:10 AM, Bonita Montero wrote:
Also, most of my tuples are built at compile time. They have really
worked well for us for over 20 years. I just noted that I have over
2,900 tuples, tupleStrings, and tupleTuples in my code.

Lynn


Bonita Montero

unread,
Jul 27, 2022, 11:42:55 PM7/27/22
to
That's really stupid.

Bonita Montero

unread,
Jul 27, 2022, 11:50:56 PM7/27/22
to
It can be even simpler:

template<typename ... Ts>
requires (sizeof ...(Ts) >= 1) && (convertible_to<Ts,
tuple_element_t<0, tuple<Ts ...>>> && ...)
vector<tuple_element_t<0, tuple<Ts ...>>> myTuple( Ts &&... params )
{
vector<tuple_element_t<0, tuple<Ts ...>>> vt;
vt.reserve( sizeof ...(Ts) );
(vt.emplace_back( forward<Ts>( params ) ), ...);
return vt;
}

Lynn McGuire

unread,
Jul 28, 2022, 2:32:24 PM7/28/22
to
Thank you.

Lynn

Cholo Lennon

unread,
Jul 30, 2022, 5:04:08 PM7/30/22
to
Thanks for the clarification, I wrongly assumed that in this newsgroup,
everyone knows more or less, when some features where implemented.

Regards

Cholo Lennon

unread,
Jul 30, 2022, 5:05:08 PM7/30/22
to
Thanks, very useful :-) I didn't know it.

Lynn McGuire

unread,
Aug 3, 2022, 12:11:54 AM8/3/22
to
I had some serious problems in the port from Smalltalk to C++. The
biggest problem was that objects in Smalltalk are not typed until
runtime (all declared as type var). Whereas, objects in C++ are typed
at compile time (mostly kinda). I used my tuple methods and many other
methods to help with the typing, it made our jobs easier as my
handwritten conversion tool typed all objects as ObjPtr. After
conversion, the programmer typed all of the objects in the converted C++
code. If there was a mistake, the compiler usually yelled at us.

Lynn


0 new messages