在完美转发功能中公开参数类型,避免代码重复

时间:2018-05-16 11:22:24

标签: c++ c++11 variadic-templates dry perfect-forwarding

我有一个恼人的场景,我需要推迟某个对象state的初始化,并允许用户按需构建一个。 E.g。

// 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的界面告诉用户 nothing 有关如何初始化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存在代码重复,需要手动维护,以防参数类型发生变化。

有什么方法可以让两全其美(DRY和用户友好的界面)?请注意,state不可移动/可复制。

3 个答案:

答案 0 :(得分:3)

课程state可能无法复制/移动,但似乎ABC是。 (所以我假设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));
    }
};

答案 1 :(得分:2)

  

但这有一个主要缺点:与state :: state存在代码重复,需要手动维护以防参数类型发生变化。

这是封装的一般问题。这是(定义)不是DRY。

有一种方法可以保留state构造函数重载与initialize_state的接口之间的关系,即使用enable_ifis_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)...);
    }
};

答案 2 :(得分:2)

可能创建的问题多于解决问题,您可以模拟您的课程:

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可以显示预期的参数)。