在完美转发函数中公开参数类型,避免代码重复
Exposing parameter types in a perfectly-forwarding function avoiding code repetition
我有一个烦人的场景,我需要推迟某些对象的初始化 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
存在代码重复,需要手动维护以防参数类型发生变化。
有什么方法可以两全其美(DRY 和用户友好的界面)?请注意 state
不是 movable/copyable.
class state
可能不是 copyable/movable,但 A
、B
和 C
似乎是。 (所以我假设 state
中还有其他一些内部数据阻止了 copyability/movability)
您可以将这些成员拉出到另一个可以注入 state
的 class 中。由于没有更好的名字,我将其命名为 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));
}
};
but this has a major drawback: there is code duplication with state::state, that needs to be manually maintained in case the argument types change.
这是封装的普遍问题。它(定义)不是干的。
有一种方法可以保留 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)...);
}
};
可能制造的问题多于解决的问题,您可以模板化您的 class:
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>;
由于模板由 class 固定,void initialize_state(Ts&&... xs)
具有固定的签名(例如,智能感知可以显示预期的参数)。
我有一个烦人的场景,我需要推迟某些对象的初始化 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
存在代码重复,需要手动维护以防参数类型发生变化。
有什么方法可以两全其美(DRY 和用户友好的界面)?请注意 state
不是 movable/copyable.
class state
可能不是 copyable/movable,但 A
、B
和 C
似乎是。 (所以我假设 state
中还有其他一些内部数据阻止了 copyability/movability)
您可以将这些成员拉出到另一个可以注入 state
的 class 中。由于没有更好的名字,我将其命名为 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));
}
};
but this has a major drawback: there is code duplication with state::state, that needs to be manually maintained in case the argument types change.
这是封装的普遍问题。它(定义)不是干的。
有一种方法可以保留 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)...);
}
};
可能制造的问题多于解决的问题,您可以模板化您的 class:
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>;
由于模板由 class 固定,void initialize_state(Ts&&... xs)
具有固定的签名(例如,智能感知可以显示预期的参数)。