Как я могу запретить заводскому пользователю вызывать неправильный шаблонный или перегруженный метод на основе enum?

#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. Поскольку у меня всего несколько действий, ваше решение работает для меня, большое вам спасибо, сэр :).