#c #overloading #template-specialization #compile-time
#c #перегрузка #шаблон-специализация #время компиляции
Вопрос:
Я создаю фабрику для команд, прямо сейчас я могу разделить свои команды на два отдельных класса: те, для которых требуется адрес, и те, для которых его нет.
Просто для ясности, это часть встроенного проекта, в котором мы решили не использовать динамическое распределение и перенести все, что мы могли, во время компиляции, поэтому это должно быть подтверждено во время компиляции.
Я хочу иметь заводской метод с именем newCommand, который принимает параметр действия и может принимать параметр адреса (если для типа команды требуется адрес) и возвращает команду (в моем случае это буфер с предопределенным размером для каждого из типов команд).
Одна вещь, которую я пробовал, — это перегрузка функций:
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 только с одним параметром, передающим действие, для КОТОРОГО ТРЕБУЕТСЯ адрес. Опять же, я мог бы проверить это во время выполнения, но это не то, что я хочу.
Еще одной вещью, которую я пробовал, было введение другого перечисления, называемого 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:
Один из вариантов — изменить ваш первый пример и использовать шаблонные методы с помощью 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);
}
Комментарии:
1. Поскольку у меня всего несколько действий, ваше решение работает для меня, большое вам спасибо, сэр :).