You're asking the wrong question.
The right question is "who should be responsible for ensuring that the desired values are within the limitations required by the data type?"
Let's look at your cases:
void function1(int16_t health, int8_t mana)
object.health = health;
object.mana = mana;
void function2(int health, int mana)
object.health = static_cast<int16_t>(health); // Cutting int
object.mana = static_cast<int8_t>(mana); // Cutting int
The interface of
function1 has an explicit size requirement on its types. It is therefore the responsibility of everyone who calls that function to verify that the values that they want to store fit within the sizes specified in the interface.
function2 by contrast has an implicit size requirement. It says that it takes
ints. But it doesn't really; if you pass it values outside of the size of the data type, then it invokes implementation-defined behavior.
In some respects,
function1 is better. The sizes for the parameters are explicitly stated. But because of C++'s conversion rules, something as obviously broken as this won't cause even a compile error:
That is a narrowing conversion, which is allowed. It invokes implementation-defined behavior, which is probably not what you want.
Thus, the real question is... what do you want?
If the user wants to set the health to more than 0x7FFF, what should your code do? Should it silently accept it and invoke implementation-defined behavior? Or should it provoke an error condition, throw an exception or just flat-out terminate?
If you want to do any of the latter, then you must use
function2 (or rather, an error-checking version of
function2). By using it, the implicit interface requirement can at least be verified. This makes it possible to track down code that wants to set the value improperly.