Предоставление типов параметров в идеальной функции пересылки, избегая повторения кода - PullRequest
0 голосов
/ 16 мая 2018

У меня есть раздражающий сценарий, в котором мне нужно отложить инициализацию некоторого объекта state и позволить пользователю создать его по требованию. Э.Г.

// user code

context c;
// ...do something...
c.initialize_state(a, b, c);

// library code

class context
{
private:
    class state
    {
        state(A a, B b, C c);

        state(const state&) = delete;
        state(state&&) = delete;
    };

    std::optional<state> _state; // or `boost::optional`

public:
    template <typename... Xs>
    void initialize_state(Xs&&... xs) 
    {
        _state.emplace(std::forward<Xs>(xs)...);
    }
};

Как видно из приведенного выше кода, интерфейс context::initialize_state сообщает пользователю ничего о том, как инициализировать context::_state. Пользователь вынужден посмотреть на реализацию initialize_state, а затем посмотреть на state::state, чтобы понять, что следует передать на initialize_state.

Я мог бы изменить initialize_state на ...

void initialize_state(A&& a, B&& b, C&& c) 
{
    _state.emplace(std::move(a), std::move(b), std::move(c));
}

... но это имеет существенный недостаток: существует дублирование кода с state::state, которое необходимо поддерживать вручную в случае изменения типов аргументов.

Можно ли как-то получить лучшее из обоих миров (СУХОЙ и удобный интерфейс)? Обратите внимание, что state нельзя перемещать / копировать.

Ответы [ 3 ]

0 голосов
/ 16 мая 2018

но у этого есть главный недостаток: есть дублирование кода с помощью state :: state, которое необходимо поддерживать вручную в случае изменения типов аргументов.

Это общая проблема с инкапсуляцией. Это (определение) не СУХОЙ.

Существует способ сохранить взаимосвязь между перегрузками конструктора state и интерфейсом initialize_state, который должен использовать enable_if вместе с признаком типа is_constructible.

class context
{
private:
    class state
    {
    public:
        state(A a, B b, C c);

        state(const state&) = delete;
        state(state&&) = delete;
    };

    std::optional<state> _state; // or `boost::optional`
public:
    template <typename... Xs>
    auto 
    initialize_state(Xs&&... xs) 
    -> 
    std::enable_if_t
    <
        // condition
        std::is_constructible<state, Xs...>::value, 

        // return type
        void
    >
    {
        _state.emplace(std::forward<Xs>(xs)...);
    }
};
0 голосов
/ 16 мая 2018

Вероятно, создавая больше проблем, чем решая, вы можете создать шаблон для своего класса:

template <typename ... Ts>
class context_impl
{
private:
    class state
    {
        state(Ts...);

        state(const state&) = delete;
        state(state&&) = delete;
    };

    std::optional<state> _state; // or `boost::optional`

public:
    void initialize_state(Ts&&... xs) 
    {
        _state.emplace(std::forward<Xs>(xs)...);
    }
};

using context = context_impl<A, B, C>;

Поскольку шаблон фиксируется классом, void initialize_state(Ts&&... xs) имеет фиксированную подпись (например, intellisense может показывать ожидаемые аргументы).

0 голосов
/ 16 мая 2018

Класс state может не быть копируемым / перемещаемым, но кажется, что A, B и C. (Так что я предполагаю, что в state есть некоторые другие внутренние данные, которые не позволяют копировать / перемещать)

Вы можете вытащить этих членов в другой класс, который можно добавить в state. Из-за отсутствия лучшего имени я назову это state_args:

struct state_args
{
   explicit state_args(A a, B b, C c);
   A a_;
   B b_;
   C c_;
};

Что позволяет следующее:

class context
{
private:
    class state
    {
        state(state_args args);

        state(const state&) = delete;
        state(state&&) = delete;
    };

    std::optional<state> _state; // or `boost::optional`

public:
    template<class STATE_ARGS, /*enable_if to ensure STATE_ARGS is indeed state_args*/>
    void initialize_state(STATE_ARGS&& internal_state) 
    {
        _state.emplace(std::forward<STATE_ARGS>(internal_state));
    }
};
...