c++templatesmetaprogrammingc++20chip-8

How can i pass an element to be able to evaluate within a certain specialization?


I am trying to make a CHIP-8 emulator that generates most of the instructions at compile time.

So i have a constexpr array like this:

constexpr uint16_t hardCodedROM[] = {
    0x8001, 0x8002, 0x8003, 0xA0F0, 0xB0FE
};

And I have a specialization for each item of the array like this:

template<uint16_t instruction> struct Opcode 
{
    static inline void Task(uint16_t code)
    { 
        printf("Unknown Opcode \n");
    }
};

template<> struct Opcode<0x8000>
{

    static inline auto Task(uint16_t code)
    {
        static constexpr unsigned char submask = code & 0x000F; //error here, code can not use as a constant

        if constexpr (submask == 0x0001)
        {
            printf("Opcode 0x8xx1 \n");
        }

        if constexpr (submask == 0x0002)
        {
            printf("Opcode 0x8xx2 \n");
        }

        if constexpr (submask == 0x0003)
        {
            printf("Opcode 0x8xx2 \n");
        }

        if constexpr (submask == 0x0000)
        {
            printf("Opcode 0x8xx0 \n");
        }
    }
};

template<> struct Opcode<0xA000>
{
    static inline auto Task(uint16_t code)
    {
        printf("Opcode 0xAxxx \n");
    }
};

template<> struct Opcode<0xB000>
{
    static inline auto Task(uint16_t code)
    {
        printf("Opcode 0xBxxx \n");
    }
};

And i iterate over each of the elements and invoke specialization in this way:

[&] <std::size_t...p>(std::index_sequence<p...>)
{
    (Opcode<(hardCodedROM[p] & 0xF000)>::Task(hardCodedROM[p]), ...); //How should i pass the element, to be able to evaluate depending on each submask?
}
(std::make_index_sequence<5>{});

How should i pass the element (hardCodedROM[p]), to be able to evaluate depending on each submask within a certain specialization?


Solution

  • If code is always known at compile time, make it a template parameter:

    template<std::uint16_t code>
    static void Task() {
        constexpr unsigned char submask = code & 0x000F;
        // ...
    }
    

    and then don't forget the template disambiguator:

    [&]<std::size_t...p>(std::index_sequence<p...>) {
        (Opcode<(hardCodedROM[p] & 0xF000)>::template Task<hardCodedROM[p]>(), ...);
    }
    (std::make_index_sequence<5>{});
    

    You can also wrap code into a type similar to Opcode and pass it as a function parameter to avoid pretty ugly template keyword. For example:

    template<std::uint16_t> struct Instruction {};
    
    // ...
        template<std::uint16_t code>
        static void Task(Instruction<code>) { 
            constexpr unsigned char submask = code & 0x000F;
            // ...
        }
    // ...
    
    [&]<std::size_t...p>(std::index_sequence<p...>) {
        (Opcode<(hardCodedROM[p] & 0xF000)>::Task(Instruction<hardCodedROM[p]>{}), ...);
    }
    (std::make_index_sequence<5>{});