简体   繁体   中英

How could I prevent the factory user from calling the wrong templated or overloaded method based on enum?

I'm creating a factory for commands, right now I can separate my commands into two distintic classes, the ones that requires an address and the ones that doesn't.

Just to be clear, it's part of an embedded project in which we've decided not to use dynamic allocation and bring everything we could to compile time, so it has to be asserted at compile time.

I wanna have a factory method called newCommand which takes an Action parameter and might take an address paramater (if the command type requires an address) and returns a Command (in my case, it's a buffer with a predefined size for each of the command types).

One thing I've tried is function overloading:

struct Driver
{
    enum Action
    {
        PAGE_PROGRAM = 0x2,             // requires address
        SECTOR_ERASE = 0x20,            // requires address
        WRITE_DISABLE = 0x4,            // doesn't require address
        WRITE_ENABLE = 0x6              // doesn't require address
    };
};

struct DriverCommandFactory
{
    static dummy_buffer<1> newCommand(Driver::Action command)
    {
        dummy_buffer<1> ret;
        return ret;
    }

    static dummy_buffer<4> newCommand(Driver::Action command, uint32_t address)
    {
        dummy_buffer<4> ret;
        return ret;
    }
};

But this approach has a "problem", the user (factory consumer) can still call the newCommand version with only one paramater passing an Action which NEEDS an address. Again, I could check it at run-time, but that's not what I want.

Another thing I've tried was the introduction of another enum, called CommandType, and the use of explicit (full) template specialization:

template <Driver2::CommandType TYPE>
struct DriverCommandFactory2;

template <>
struct DriverCommandFactory2 <Driver2::CommandType::REQUIRES_ADDRESS>
{
    static dummy_buffer<4> newCommand(Driver::Action command, uint32_t address)
    {
        dummy_buffer<4> ret;
        return ret;
    }
};

template <>
struct DriverCommandFactory2 <Driver2::CommandType::DOESNT_REQUIRE_ADDRESS>
{
    static dummy_buffer<1> newCommand(Driver::Action command)
    {
        dummy_buffer<1> ret;
        return ret;
    }
};

This one "doesn't" allow the user to call the wrong method version, which is what the previous solution couldn't do. But it introduces another problem, it forces the user to specify the command type as a template argument, which is redundant, because the action itself is enough to know that.

So, is there a way I can prevent the user from calling the wrong method without forcing him to specify some template argument elegantly? Again, this has to be checked at compile time.

If it's any help, I have a C++17 compiler available.

One of the options is to modify your first example and use template methods with std::enable_if (I've added some includes and empty defines to make the example compilable):

#include <cstdint>
#include <type_traits>

template <int>
struct dummy_buffer {};

struct Driver
{
    enum Action
    {
        PAGE_PROGRAM = 0x2,             // requires address
        SECTOR_ERASE = 0x20,            // requires address
        WRITE_DISABLE = 0x4,            // doesn't require address
        WRITE_ENABLE = 0x6              // doesn't require address
    };
};

template <Driver::Action Action>
struct RequiresAddress;

template <>
struct RequiresAddress<Driver::Action::PAGE_PROGRAM> 
    : public std::true_type {};
template <>
struct RequiresAddress<Driver::Action::SECTOR_ERASE> 
    : public std::true_type {};
template <>
struct RequiresAddress<Driver::Action::WRITE_DISABLE> 
    : public std::false_type {};
template <>
struct RequiresAddress<Driver::Action::WRITE_ENABLE> 
    : public std::false_type {};


struct DriverCommandFactory
{
    template <Driver::Action Command, typename = std::enable_if_t<RequiresAddress<Command>::value>>
    static dummy_buffer<1> newCommand()
    {
        dummy_buffer<1> ret;
        return ret;
    }

    template <Driver::Action Command, typename = std::enable_if_t<!RequiresAddress<Command>::value>>
    static dummy_buffer<4> newCommand(uint32_t address)
    {
        dummy_buffer<4> ret;
        return ret;
    }
};

void foo()
{
    auto c1 = DriverCommandFactory::newCommand<Driver::Action::SECTOR_ERASE>();
    auto c2 = DriverCommandFactory::newCommand<Driver::Action::WRITE_DISABLE>(42);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM