Is Main() Overloaded in C++

Is main() overloaded in C++?

§3.6.1/2 (C++03) says

An implementation shall not predefine
the main function. This function shall
not be overloaded.
It shall have a
return type of type int, but otherwise
its type is implementation-defined.
All implementations shall allow both
of the following definitions of main:

   int main() { /* ... */ }
int main(int argc, char* argv[]) { /* ... */ }

You can use either of them. Both are standard compliant.

Also, since char *argv[] is equivalent to char **argv, replacing char *argv[] with char **argv doesn't make any difference.


But both the versions cannot co-exist at the same time ! (use case can be like: while running the binary from command prompt, if you pass no argument then 1st version should be called else the 2nd version).

No. Both versions cannot co-exist at the same time. One program can have exactly one main function. Which one, depends on your choice. If you want to process command-line argument, then you've to choose the second version, or else first version is enough. Also note that if you use second version, and don't pass any command line argument, then there is no harm in it. It will not cause any error. You just have to interpret argc and argv accordingly, and based on their value, you've to write the logic and the flow of your program.

Can we overload main() function in C++?

Yes it explicitly forbids that. Refer to 3.6.1p2

An implementation shall not predefine the main function. This function shall not be overloaded.


This way, the main function's name can stay unmangled. That is, the runtime library can call a symbol having a fixed name (e.g main or _main) to jump to the main function. The libraries' code will not need to depend on what parameter list the program's main function has.

The implementation is also allowed to define additional valid parameter lists for the main function (The POSIX spec specifies a char **env parameter for the environment variables, for example). It would not be clear when an overload of main is a "non-main function" or whether it's a "main function", thus an entry point. Presumably, you would want to get an error if you would declare more than one entry point, so such issues are important.

How to achieve function overloading in C?

There are few possibilities:

  1. printf style functions (type as an argument)
  2. opengl style functions (type in function name)
  3. c subset of c++ (if You can use a c++ compiler)

Does C support overloading?

No, C doesn't support any form of overloading (unless you count the fact that the built-in operators are overloaded already, to be a form of overloading).

printf works using a feature called varargs. You make a call that looks like it might be overloaded:

printf("%d", 12); // int overload?
printf("%s", "hi"); // char* overload?

Actually it isn't. There is only one printf function, but the compiler uses a special calling convention to call it, where whatever arguments you provide are put in sequence on the stack[*]. printf (or vprintf) examines the format string and uses that to work out how to read those arguments back. This is why printf isn't type-safe:

char *format = "%d";
printf(format, "hi"); // undefined behaviour, no diagnostic required.

[*] the standard doesn't actually say they're passed on the stack, or mention a stack at all, but that's the natural implementation.

How does the main() method work in C?

Some of the features of the C language started out as hacks which just happened to work.

Multiple signatures for main, as well as variable-length argument lists, is one of those features.

Programmers noticed that they can pass extra arguments to a function, and nothing bad happens with their given compiler.

This is the case if the calling conventions are such that:

  1. The calling function cleans up the arguments.
  2. The leftmost arguments are closer to the top of the stack, or to the base of the stack frame, so that spurious arguments do not invalidate the addressing.

One set of calling conventions which obeys these rules is stack-based parameter passing whereby the caller pops the arguments, and they are pushed right to left:

 ;; pseudo-assembly-language
;; main(argc, argv, envp); call

push envp ;; rightmost argument
push argv ;;
push argc ;; leftmost argument ends up on top of stack

call main

pop ;; caller cleans up
pop
pop

In compilers where this type of calling convention is the case, nothing special need to be done to support the two kinds of main, or even additional kinds. main can be a function of no arguments, in which case it is oblivious to the items that were pushed onto the stack. If it's a function of two arguments, then it finds argc and argv as the two topmost stack items. If it's a platform-specific three-argument variant with an environment pointer (a common extension), that will work too: it will find that third argument as the third element from the top of the stack.

And so a fixed call works for all cases, allowing a single, fixed start-up module to be linked to the program. That module could be written in C, as a function resembling this:

/* I'm adding envp to show that even a popular platform-specific variant
can be handled. */
extern int main(int argc, char **argv, char **envp);

void __start(void)
{
/* This is the real startup function for the executable.
It performs a bunch of library initialization. */

/* ... */

/* And then: */
exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}

In other words, this start module just calls a three-argument main, always. If main takes no arguments, or only int, char **, it happens to work fine, as well as if it takes no arguments, due to the calling conventions.

If you were to do this kind of thing in your program, it would be nonportable and considered undefined behavior by ISO C: declaring and calling a function in one manner, and defining it in another. But a compiler's startup trick does not have to be portable; it is not guided by the rules for portable programs.

But suppose that the calling conventions are such that it cannot work this way. In that case, the compiler has to treat main specially. When it notices that it's compiling the main function, it can generate code which is compatible with, say, a three argument call.

That is to say, you write this:

int main(void)
{
/* ... */
}

But when the compiler sees it, it essentially performs a code transformation so that the function which it compiles looks more like this:

int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
/* ... */
}

except that the names __argc_ignore don't literally exist. No such names are introduced into your scope, and there won't be any warning about unused arguments.
The code transformation causes the compiler to emit code with the correct linkage which knows that it has to clean up three arguments.

Another implementation strategy is for the compiler or perhaps linker to custom-generate the __start function (or whatever it is called), or at least select one from several pre-compiled alternatives. Information could be stored in the object file about which of the supported forms of main is being used. The linker can look at this info, and select the correct version of the start-up module which contains a call to main which is compatible with the program's definition. C implementations usually have only a small number of supported forms of main so this approach is feasible.

Compilers for the C99 language always have to treat main specially, to some extent, to support the hack that if the function terminates without a return statement, the behavior is as if return 0 were executed. This, again, can be treated by a code transformation. The compiler notices that a function called main is being compiled. Then it checks whether the end of the body is potentially reachable. If so, it inserts a return 0;

How c++ deduce the right overloaded function?

tl;dr:

  • std::pow is a difficult example due to compilers providing more overloads & the c version being considered a builtin in gcc, so your problem is rather hard to demonstrate with it.
  • yes the call will actually be ambigous if the 5 functions you listed are the only ones competing in overload resolution.

pow() is a rather tricky function to use for this, since there are a few gotchas:

  • In your godbolt example your actually calling std::pow<int, int>, i.e. a templated version of it. This is because you're using a rather new version of gcc that provides a templated overload of std::pow for better performance.

    You can see this quite nicely in the generated assembly, line 6: (look at the end)

    call    __gnu_cxx::__promote_2<int, int, __gnu_cxx::__promote<int, std::__is_integer<int>::__value>::__type, __gnu_cxx::__promote<int, std::__is_integer<int>::__value>::__type>::__type std::pow<int, int>(int, int)

    So for testing this you could do a few things:

    • switch to an older version of gcc that doesn't have the templated overload yet, i.e. gcc 4.1.2:

      godbolt example
    • or test it with user-specified functions, like the example from @mch:

      godbolt example
  • Another problem is <cmath> itself - since it is the c++ equivalent of <math.h> in c, gcc and clang do expose the c pow function as well in it (without std:: prefix) - this is why your code works even when removing using namespace std;.

    Since c doesn't allow function overloads there's only one version with double's. (the float version is powf, the long double one powl).

    Additionally gcc considers them to be built-ins, so in this case it can just completely get rid of the pow call since it uses constants as arguments, even at -O0.

    This is what happened in the example provided by @ Aanchal Sharma: godbolt example

    mov     rax, QWORD PTR .LC0[rip]
    movq xmm0, rax
    // calculated value (64 as a double)
    .LC0:
    .long 0
    .long 1078984704

    so gcc just calculated the result at compile-time and inlined it. (note there's no overload resolution problem at all here, since there's only one pow function in c - and the c++ functions aren't available because using namespace std; is not commented in)

    You can suppress this by passing -fno-builtin as a compiler argument, in which case gcc will dutifully emit the call to double pow(double, double): godbolt example

    movsd   xmm0, QWORD PTR .LC0[rip]
    mov rax, QWORD PTR .LC1[rip]
    movapd xmm1, xmm0
    movq xmm0, rax
    call pow

    .LC0: // 3 as a double
    .long 0
    .long 1074266112
    .LC1: // 4 as a double
    .long 0
    .long 1074790400

So to answer your questions:

1. Why does it work?

Because gcc actually provides a templated overload of std::pow that is a perfect match in this case:

template<typename _Tp, typename _Up>
inline typename __gnu_cxx::__promote_2<_Tp, _Up>::__type pow(_Tp __x, _Up __y);

2. Assuming there would be no template overload, how would the compiler decide which overload to use?

The rules for determining the best overload are rather complicated, so i'll simplify them a bit (i'll completely ignore user-defined conversion functions, templated functions, variadic functions and a few other things not relevant to the question)

If you want to get a more complete picture of the actual rules you can take a look at the c++98 standard, section 13.3 Overload resolution is what you're looking for.

Now to get into it:

  • First all functions are eliminated that don't have the right number of arguments. i.e. if there would be a pow function taking three arguments (and the third argument doesn't have a default value) - it would be directly eliminated.

    13.3.2 Viable functions

    (1) First, to be a viable function, a candidate function shall have enough parameters to agree in number with the
    arguments in the list.

  • Secondly the parameters you provided actually need to be convertible to the parameters of the overload in question. If there's no way to convert between them the overload would be eliminated.

    13.3.2 Viable functions

    (3) Second, for F to be a viable function, there shall exist for each argument an implicit conversion sequence
    (13.3.3.1) that converts that argument to the corresponding parameter of F.

  • After that the compiler has a set of overloads left - the standard calls them Viable functions - that could be called in the given context. Now the compiler needs to actually decide which of those is the best one, i.e. the Best Viable Function, that should end up being called.

  • Now we get to the fun part - rating overloads based on how good of a fit they are. There are basically three primary categories a parameter conversion can have:

    Exact Match > Promotion > Conversion ¹

    • Exact Match is the best one, this applies if the types match exactly or if it's just decaying an array to a pointer or function to a function pointer.
    • Promotion is slightly worse, this applies if you e.g. convert from one integral type to a bigger one or from a floating point type to a bigger one (but not between integral and floating points, and not to smaller types)
    • Conversion is the worst one and covers "downgrading" of int and float types (e.g. int -> char, double -> float) and conversion between integral and floating point types, e.g. int -> double.

    The compiler doesn't care what the exact conversion is, only in which of the three categories it falls. ²

    So you could think of it that way: ³

    • Each function gets a score based on the conversions needed for the arguments
    • Exact Match is worth 0 points
    • Promotion is worth -1 points
    • Conversion is worth -2 points
    • the function with the highest score gets called
    • it there's a draw, the call is ambigous
  • So now to actually rank the std::pow overloads:

    // when called as pow(1, 1)

    // 2x Conversion -> score -4
    double pow (double base, double exponent);

    // 2x Conversion -> score -4
    float pow (float base, float exponent);

    // 2x Conversion -> score -4
    long double pow (long double base, long double exponent);

    // 1x Conversion, 1x Exact -> score -2
    double pow (double base, int exponent);

    // 1x Conversion, 1x Exact -> score -2
    long double pow (long double base, int exponent);

    So the first 3 are already off the table, because their score is worse.

    This leaves the last 2, but those actually have the same score, so it's a draw!

    -> You'll get an ambigous function call error

Footnotes:

¹ there are a few more rules for user-defined conversion functions, vararg functions and templates

  (see 13.3.3.1 Implicit conversion sequences)


² In reality there are quite a few more things to consider

  (see 13.3.3.2 Ranking implicit conversion sequences for a complete list)


³ Extremely simplified version. This is not how an actual compiler would do it.

Overloaded ++ operator only works from left side (C++)

Color& operator++(Color& source) is for pre-incremant,

you need

Color operator++(Color& source, int) for post increment.



Related Topics



Leave a reply



Submit