我如何防止工厂用户基于枚举调用错误的模板化或重载方法?
How could I prevent the factory user from calling the wrong templated or overloaded method based on enum?
我正在为命令创建一个工厂,现在我可以将我的命令分成两个不同的 类,一个需要地址,另一个不需要。
需要说明的是,它是嵌入式项目的一部分,在该项目中我们决定不使用动态分配并将我们能做的一切都带到编译时,因此必须在编译时断言它。
我想要一个名为 newCommand 的工厂方法,它接受一个 Action 参数并可能接受一个地址参数(如果命令类型需要地址)和 returns 一个命令(在我的例子中,它是一个缓冲区每个命令类型的预定义大小)。
我试过的一件事是函数重载:
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;
}
};
但是这种方法有一个"problem",用户(工厂消费者)仍然可以调用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;
}
};
这个"doesn't"允许用户调用错误的方法版本,这是以前的解决方案无法做到的。但是它引入了另一个问题,它强制用户将命令类型指定为模板参数,这是多余的,因为动作本身就足以知道了。
那么,有没有一种方法可以防止用户调用错误的方法而不用强制他优雅地指定一些模板参数?同样,这必须在编译时检查。
如果有任何帮助,我有一个 C++17 编译器可用。
其中一个选项是修改您的第一个示例并使用带有 std::enable_if 的模板方法(我添加了一些包含和空定义以使示例可编译):
#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);
}
我正在为命令创建一个工厂,现在我可以将我的命令分成两个不同的 类,一个需要地址,另一个不需要。
需要说明的是,它是嵌入式项目的一部分,在该项目中我们决定不使用动态分配并将我们能做的一切都带到编译时,因此必须在编译时断言它。
我想要一个名为 newCommand 的工厂方法,它接受一个 Action 参数并可能接受一个地址参数(如果命令类型需要地址)和 returns 一个命令(在我的例子中,它是一个缓冲区每个命令类型的预定义大小)。
我试过的一件事是函数重载:
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;
}
};
但是这种方法有一个"problem",用户(工厂消费者)仍然可以调用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;
}
};
这个"doesn't"允许用户调用错误的方法版本,这是以前的解决方案无法做到的。但是它引入了另一个问题,它强制用户将命令类型指定为模板参数,这是多余的,因为动作本身就足以知道了。
那么,有没有一种方法可以防止用户调用错误的方法而不用强制他优雅地指定一些模板参数?同样,这必须在编译时检查。
如果有任何帮助,我有一个 C++17 编译器可用。
其中一个选项是修改您的第一个示例并使用带有 std::enable_if 的模板方法(我添加了一些包含和空定义以使示例可编译):
#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);
}