LeetCode 639: Decode Ways II
Make helper functions private
Member functions that are not part of the public API should be marked private.
You should know that by now :)
Use uint64_t instead of size_t
There is no guarantee that size_t is big enough for the calculations you are doing. While you might only need 32 bits to store the results, you need to do the calculations using 64 bit integers (because you are multiplying two numbers that are each up to 30 bits in size). So to be safe, I would use uint64_t. You could use uint32_t as well, but then you need to explicitly cast to uint64_t before doing the multiplications inside numDecodings().
Use size_t for sizes and counts, but not for other purposes.
Make the decode() functions constexpr
I see you made MOD constexpr, which is great, but you can make the decode() functions constepxr as well.
Naming things
a_num_ast and b_num_ast are weird looking names. I'm guessing by a_num_ast you mean &quot;a variable that can hold a number or an asterisk&quot;. But you shouldn't try to encode the type in the variable name. Just use a and b here.
What does decodes_dp mean? I would try to give it a better name. Use nouns for variables. Perhaps number_of_possibilities, or num_decodings (although that almost clashes with the function name).
Use std::array for fixed-length vectors
This avoids unnecessary heap allocations. So:
std::array&lt;uint64_t, 3&gt; decodes_dp{1, decode(message[0]), 0};

Remove unnecessary modulo operations
In the following expression:
decodes_dp[index % 3] = (
        decodes_dp[(index - 1) % 3] * decode(message[index - 1]) % MOD +
        decodes_dp[(index - 2) % 3] * decode(message[index - 2], message[index - 1]) % MOD
    ) % MOD;

Since you already need to use uint64_t for the result of the multiplications to not wrap, you don't need the modulo operations inside the outermost parentheses.
Consider using switch-statements
Your decode() functions can be rewritten as follows:
inline uint64_t Solution::decode(const char a) {
    switch(a) {
        case '0':
            return 0;
        case '*':
            return 9;
        default:
            return 1;
    }
}

inline uint64_t Solution::decode(const char a, const char b) {
    switch(a) {
        case '0':
            return 0;
        case '1':
            return b == '*' ? 9 : 1;
        case '2':
            switch(b) {
                case '0'...'6':
                    return 1;
                case '*':
                    return 6;
                default:
                    return 0;
            }
        case '*':
            return decode('1', b) + decode('2', b);
        default:
            return 0;
    }
}

It's more compact and avoids repeating a lot of if (a_num_ast ...), making it easier to see the structure of your code.