如何防止工厂用户基于枚举调用错误的模板化或重载方法?

时间:2019-04-11 15:59:21

标签: c++ overloading template-specialization compile-time

我正在为命令创建工厂,现在我可以将命令分为两个不同的类,一个是需要地址的类,另一个是不需要地址的类。

需要明确的是,它是嵌入式项目的一部分,在该项目中,我们决定不使用动态分配,而是将所有可能的东西带到编译时,因此必须在编译时声明。

我想有一个名为newCommand的工厂方法,该方法带有一个Action参数,并且可能带有一个地址参数(如果命令类型需要一个地址)并返回一个Command(在我的情况下,这是一个缓冲区,每个缓冲区的大小都具有预定义的大小)命令类型)。

我尝试过的一件事是函数重载:

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;
    }
};

但是这种方法有一个“问题”,用户(工厂使用者)仍然可以调用newCommand版本,而只有一个参数传递需要地址的Action。同样,我可以在运行时检查它,但这不是我想要的。

我尝试过的另一件事是引入了另一个枚举CommandType,并使用了显式(完整)模板特化:

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;
    }
};

此“不允许”允许用户调用错误的方法版本,而这是以前的解决方案无法做到的。但这带来了另一个问题,它迫使用户将命令类型指定为模板参数,这是多余的,因为操作本身足以知道这一点。

那么,有没有一种方法可以防止用户调用错误的方法而不强迫他优雅地指定一些模板参数?同样,必须在编译时检查它。

如果有帮助,我有一个C ++ 17编译器。

1 个答案:

答案 0 :(得分:1)

其中一个选项是修改您的第一个示例,并使用带有std :: enable_if的模板方法(我添加了一些include和空定义以使示例可编译):

#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);
}