A multiplication a * b of two unsigned (!) integer numbers of arbitrary size, let's call this type T, will overflow if and only if the result would be greater than the maximum number T can hold. The standard library can access the maximum number of any type T using std::numeric_limits.
The above statement can also be written as: a * b will overflow if and only if a > max(T) / b, which is the same as b > max(T) / a (where / is an integer division and max(T) is the maximum number T can hold).
Example: Let's say T = uint8_t, so max(T) == 255. A couple of examples for demonstration:
a              |  16 |  15 |  14
b              |  16 |  17 |  18
---------------------------------
a * b          | 256 | 255 | 252
overflow?      | yes |  no |  no
---------------------------------
max(T)/b       |  15 |  15 |  14
a > max(T)/b?  | yes |  no |  no
Use this method to check if a multiplication a * b will overflow:
#include <limits.h>
template<typename T>
bool multiplicationWillOverflow(T a, T b) {
    return a > std::numeric_limits<T>::max() / b;
}
Then, use this method twice on your product of three numbers:
uint64_t val1, val2, val3;
if (multiplicationWillOverflow(val1, val2)) {
    //warning message
}
uint64_t product12 = val1 * val2, 
else if (multiplicationWillOverflow(product12, val3)) {
    //warning message
}
uint64_t product123 = product12 * val3;
Another option is to encapsulate the multiplication and the check in one method. Throw an exception if an overflow occurs.
template<typename T>
T safeMultiplication(T a, T b) {
    if (a > std::numeric_limits<T>::max() / b)
        throw ...;
    else
        return a * b;
}
You can encapsulate this behavior in a custom type, which overloads arithmetic operators and throws if an overflow would happen.
Don't use exceptions if you expect overflows as "normal behavior" / under "normal circumstances". In such cases, use an error parameter / a flag in a custom type instead.
A similar thing is done in floating point arithmetic with NaN exceptional states: Continuing a calculation with a NaN value will result in NaN again. A similar implementation for flagging overflow in your custom type will make sure you can detect overflow in chained calculations, like your product of three numbers, easily. The point about a custom type is that you don't have to write the expression to be calculated twice: As the calculation itself plus the overflow checking in advance.