Search code examples
c++templatesc++17variadic-templates

Pack and pass variadic template args to another template class


This code snippet implements two classes - A command handler that takes in a command name and a list of supported sub-commands and a Meta class that holds all such supported "Commands".

I'm looking for a way to initialized these top level CommandHandler objects (cmd_one_, cmd_two_) together, instead of initializing the individual objects, say like this:

MetaCommandWrapper<COMMAND_ONE,
                <cmd_one::SUBCMD_ONE,
                 cmd_one::SUBCMD_TWO
                >,
            COMMAND_TWO,
                <cmd_two::SUBCMD_ONE,
                 cmd_two::SUBCMD_TWO
                >
           > meta_commands_;

That would make it easier to loop through supported commands in MetaCommands and call the appropriate CommandHandler class's method. For example, a method execute issued like this: m.execute(COMMAND_ONE, cmd_one::SUBCMD_TWO) would execute command one's second subcommand, and so on.

How do I go about doing that?

#include <cstdint>

enum meta_commands
{
    COMMAND_ONE,
    COMMAND_TWO
};

namespace cmd_one
{
enum subcmd
{
    SUBCMD_ONE,
    SUBCMD_TWO
};
}

namespace cmd_two
{
enum subcmd
{
    SUBCMD_ONE,
    SUBCMD_TWO
};
}

template <uint32_t Command, uint32_t...SubCommands>
class CommandHandler
{
};

class MetaCommands
{
private:
    CommandHandler<meta_commands::COMMAND_ONE, cmd_one::SUBCMD_ONE, cmd_one::SUBCMD_TWO> cmd_one_;
    CommandHandler<meta_commands::COMMAND_TWO, cmd_two::SUBCMD_ONE, cmd_two::SUBCMD_TWO> cmd_two_;
};

int main()
{
    MetaCommands m;

    return 0;
}


Solution

  • I'm not sure exactly what you need but it seems like MetaCommands should be a class template since you want to create it with different template parameters:

    template <class... CmdHandlers>
    class MetaCommands {};
    

    This would then not let you create an instance exactly as you asked for, but it'll be pretty similar:

    int main() {
        MetaCommands<
            CommandHandler<COMMAND_ONE, cmd_one::SUBCMD_ONE, cmd_one::SUBCMD_TWO>,
            CommandHandler<COMMAND_TWO, cmd_two::SUBCMD_ONE, cmd_two::SUBCMD_TWO> 
        > meta_commands_;
    }
    

    You could then extract or fold over the template arguments as you wish. Say you add a function for printing the Command and SubCommands of a CommandHandler

    template <uint32_t Command, uint32_t... SubCommands>
    class CommandHandler {
        friend std::ostream& operator<<(std::ostream& os, const CommandHandler&) {
            os << "Cmd: " << Command << " Subs:";
            (..., (os << ' ' << SubCommands));
            return os;
        }
    };
    

    and add the same to MetaCommands:

    template <class... CmdHandlers>
    class MetaCommands {
    private:
        friend std::ostream& operator<<(std::ostream& os, const MetaCommands&) {
            (..., (os << CmdHandlers{} << '\n'));
            return os;
        }
    };
    

    You'd be able to print it in main like so:

    int main() {
        MetaCommands<
            CommandHandler<COMMAND_ONE, cmd_one::SUBCMD_ONE, cmd_one::SUBCMD_TWO>,
            CommandHandler<COMMAND_TWO, cmd_two::SUBCMD_ONE, cmd_two::SUBCMD_TWO> 
        > meta_commands_;
    
        std::cout << meta_commands_ << '\n';
    }
    

    Output:

    Cmd: 0 Subs: 0 1
    Cmd: 1 Subs: 0 1