"int" is now mandated by the ISO for both C and C++ as the return type for "main".
Both languages previously allowed implicit "int", and for "main" to be declared without any return type. In fact, the very first external release of C++, itself (Release E of "cfront" from February 1985), which is written in its own language, declared "main" without any return type ... but returned an integer value: the number of errors or 127, whichever was smaller
As to the question of what to return: the ISO standards for C and C++ work in synchronization with the POSIX standard. For any hosted environment conforming to the POSIX standard,
(1) 126 is reserved for the OS's shell to indicate utilities that are not executable,
(2) 127 is reserved for the OS's shell to indicate that a command that is not found,
(3) the exit values for utilities are separately spelled out on a utility-by-utility basis,
(4) programs that invoke utilities outside the shell should use similar values for their own exits,
(5) the values 128 and above are meant for use to indicate termination that results from receiving a signal,
(6) the values 1-125 are for failures,
(7) the value 0 is for success.
In C and C++ the value EXIT_SUCCESS and EXIT_FAILURE are meant for use to handle the most common situation: for programs that report a success or just a generic failure. They may, but need not, be respectively equal to 0 and 1.
That means if you want a program to return different values for different failure modes or status indications, while continuing to make use of those two constants, you might have to resort to first making sure that your additional "failure" or "status" values lie strictly between max(EXIT_SUCCESS, EXIT_FAILURE) and 126 (and hope that there's enough room in-between), and to reserve EXIT_FAILURE to mark the generic or default failure mode.
Otherwise, if you're not going to use the constants, then you should go by what POSIX mandates.
For programs meant for use on free-standing environments or on hosts that are not POSIX-compliant, I can say nothing more, except the following:
I have written free-standing programs -- as multi-threaded programs on a custom run-time system (and a custom tool-base for everything else). The general rule I followed was that:
(1) "main" ran the foreground processes, which usually consisted only of start-up, configuration or initialization routines, but could have just as well included foreground processes meant for continual operation (like polling loops),
(2) "main" returns into an infinite sleep & wait loop,
(3) no return value for "main" was defined or used,
(4) background processes ran separately, as interrupt-driven & event-driven threads, independently of "main", terminated only by the receipt of a reset signal or by other threads ... or by simply shutting off the monitoring of whatever event was driving the thread.