Контейнер шаблона класса variadi c в C ++ - PullRequest
0 голосов
/ 23 апреля 2020

Я пытаюсь написать оболочку для std::function s с типом возврата void и переменным числом std::string аргументов.

template<class... Args>
class ConsoleCommand
{
    public:
        ConsoleCommand(const std::string& name, const std::function<void(Args...)>& function) :
            name{ name },
            function{ function }
        {

        }

        void operator()(Args... args)
        {
            function(std::forward<Args>(args)...);
        }
    private:
        std::string name;
        std::function<void(Args...)> function;
};

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

ConsoleCommand<std::string> ex1; //unary function
ConsoleCommand<std::string, std::string> ex2; //dyadic function

Я хочу иметь контейнер с этими объектами (std::vector<ConsoleCommand<std::string>>), и было бы здорово, если бы я мог сделать так, чтобы аргумент шаблона класса ссылался только на тип аргументов, а не на весь пакет.

template<class Args>
class ConsoleCommand
{
    public:
        ConsoleCommand(const std::string& name, const std::function<void(Args...)>& function) :
            name{ name },
            function{ function }
        {

        }

        void operator()(Args... args)
        {
            function(std::forward<Args>(args)...);
        }
    private:
        std::string name;
        std::function<void(Args...)> function;
};

Я попробовал это, но это не сработало. Будет ли какой-нибудь обходной путь для того, чего я пытаюсь достичь?

1 Ответ

0 голосов
/ 24 апреля 2020

Вы можете рассмотреть возможность использования стирания типов, это обычный способ скрыть шаблон под информацией / полиморфизмом типов времени выполнения.

В основном у вас есть виртуальный базовый класс без функциональных возможностей или с небольшим количеством функций и производный класс, который реализует storage / access.

Затем вы можете использовать виртуальные методы или dynamic_cast для доступа к шаблонному производному классу.

Вот пример, соответствующий вашему вопросу:

class ConsoleCommand
{
private:
    // Dummy virtual base class, used to hide the template behind RTTI
    struct FunctionWrapperBase
    {
        // Needs to be virtual to enable RTTI
        virtual ~FunctionWrapperBase() {}
    };

    // Concrete type
    template<class... Args>
    struct FunctionWrapperConcrete : FunctionWrapperBase
    {
        // Just holds the function
        FunctionWrapperConcrete(std::function<void(Args...)> function)
        : function(function) {}

        std::function<void(Args...)> function;
    };

    std::string name;
    // (smart) Pointer to the virtual wrapper class
    std::unique_ptr<FunctionWrapperBase> erased_wrapper;

    // This calls the function, outside operator() so we ensure Args are all strings
    template<class... Args>
    void call_all_strings(Args... args)
    {
        auto concrete = dynamic_cast<FunctionWrapperConcrete<std::decay_t<Args>...>*>(erased_wrapper.get());
        if ( concrete )
            concrete->function(args...);
    }

public:
    template<class... Args>
    ConsoleCommand(const std::string& name, const std::function<void(Args...)>& function) :
        name{ name },
        erased_wrapper{ std::make_unique<FunctionWrapperConcrete<Args...>>(function) }
    {

    }

    template<class... Args>
    void operator()(Args&&... args)
    {
        call_all_strings(std::string(args)...);
    }
};

You может даже расширить его, чтобы вы могли хранить любые вызываемые (не только std :: function), так как это похоже на реализацию std :: function

...