Как я могу предотвратить фабричный пользователь от вызова неправильного шаблонного или перегруженного метода на основе enum? - PullRequest
0 голосов
/ 11 апреля 2019

Я создаю фабрику для команд, сейчас я могу разделить свои команды на два разных класса: те, для которых требуется адрес, и те, для которых не требуется.

Просто чтобы прояснить, эточасть встроенного проекта, в котором мы решили не использовать динамическое выделение и принести все, что могли, для компиляции времени, поэтому его нужно утверждать во время компиляции.

Я хочу иметь фабричный метод newCommand, которыйпринимает параметр Action и может принимать параметр адреса (если для типа команды требуется адрес) и возвращает команду (в моем случае это буфер с предопределенным размером для каждого из типов команд).

Oneя попробовал перегрузку функций:

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 Ответ

1 голос
/ 11 апреля 2019

Один из вариантов - изменить ваш первый пример и использовать методы шаблона с помощью 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);
}
...