c++ - 对于采用可复制和可移动类的访问者,应该有多少重载?
问题描述
我有一个Foo
可复制和可移动的课程。它可以接受和存储许多原始数字类型中的任何一种。客户端应通过TypedFoo<T>
接口检索此数据Foo
,其中T
是数字类型。当客户编写适用于多种类型的代码时,鼓励他们调用函数,作为接口的一部分发布,调用客户定义的访问者,然后在内部分派类型。这消除了客户在自己的代码中放置和维护大型 switch 语句的需要。例如,该库可以使这个 API 可用:
template<typename V>
auto apply_foo_visitor(const Foo& f, V visitor) -> decltype(visitor.template apply<int16_t>(f))
并且用户可以编写此代码来确定调用的 Foo 中单个元素的大小,myFoo
而无需知道该实例包含什么数字类型:
class GetSizeVisitor {
public:
GetSizeVisitor() {}
template<typename T>
size_t apply(const mda::TypedFoo<T> & foo) {
return sizeof(T);
}
};
// main code
size_t elementSize = apply_foo_visitor(myFoo, GetSizeVisitor());
在库中,我可以编写一个访问者的重载版本
- 按价值:
template<typename V> auto apply_foo_visitor(Foo f, V visitor)
- 作为 const lvalue ref(如上例所示):
template<typename V> auto apply_foo_visitor(const Foo & f, V visitor)
- 作为非常量左值参考:
template<typename V> auto apply_foo_visitor(Foo & f, V visitor)
- 作为右值参考:
template<typename V> auto apply_foo_visitor(Foo && f, V visitor)
我的问题是我应该提供哪些。我想给用户尽可能多的自由来编写他们的访问者,以便他们可以在需要时对 Foo 强制执行 constness,或者如果他们不这样做则允许对其进行修改,我也希望他们复制它如果他们愿意,或者如果他们不喜欢就移动它。但是,我不想在我的实现中编写冗余或不必要的代码,并且我想确保编译器能够处理客户端可能提出的访问者函数的任何合理使用。
在这方面是否有任何最佳实践?例如,对于我来说,编写一个接受左值引用的访问者版本和另一个接受右值引用的访问者版本,没有限定为 const 就足够了吗?
以下两个问题的答案:
建议最好在问题描述的场景中编写一个带有右值引用的版本,并且这将是通用的,足以正确处理左值。在我的情况下是否也是如此,我只想在适当的时候移动,否则通过 const 或非 const 引用访问?
解决方案
所以看看你的潜在apply_foo_visitor
签名:
template<typename V> auto apply_foo_visitor(Foo f, V visitor); // #1
template<typename V> auto apply_foo_visitor(const Foo & f, V visitor); // #2
template<typename V> auto apply_foo_visitor(Foo & f, V visitor); // #3
template<typename V> auto apply_foo_visitor(Foo && f, V visitor); // #4
如果用户希望能够更改原始Foo
对象,则只有 #3 或 #4 可以使用。(我假设复制 aFoo
具有值语义,而不是引用语义。)如果用户传递Foo
像变量名这样的左值表达式,#4 将不起作用。如果用户传递一个右值Foo
表达式,比如调用一个按值返回的函数Foo
,#3 将不起作用。如果用户传递左值const Foo
表达式,则 #3 或 #4 都不起作用。#1 和 #2 都将始终有效,但是将左值传递给 #1 将始终进行复制,如果访问者只想对该值进行只读访问并且不会将该值复制或移动到其他地方,则这可能是不必要的。
所以我推荐三个参考重载#2、#3 和#4。为避免过多重复,请在别处放置尽可能多的逻辑。我不知道你如何创建你的TypedFoo
界面对象,但假设Foo
有:
class Foo
{
// ...
private:
template <typename T> TypedFoo<T> typed_iface() const;
// ...
};
那么一个实现可能看起来像这样(未经测试):
namespace detail
{
template <typename Tuple, typename Enable = void>
struct common_or_void
{ using type = void; };
template <typename ... Ts>
struct common_or_void<std::tuple<Ts...>,
std::void_t<std::common_type_t<Ts...>>>
{ using type = std::common_type_t<Ts...>; };
template <typename ... Ts>
using common_or_void_t = typename common_or_void<std::tuple<Ts...>>::type;
}
class Foo
{
// ...
private:
template <typename T> TypedFoo<T> typed_iface() const;
template <typename T>
const TypedFoo<T>& transfer_cref(TypedFoo<T>& iface) const &
{ return iface; }
template <typename T>
TypedFoo<T>& transfer_cref(TypedFoo<T>& iface) &
{ return iface; }
template <typename T>
TypedFoo<T>&& transfer_cref(TypedFoo<T>& iface) &&
{ return std::move(iface); }
// F could be Foo, Foo&, const Foo&
template <typename T, typename F, typename V>
static decltype(auto) apply_as_type(F && foo, V && visitor)
{
TypedFoo<T> iface(foo);
return std::forward<V>(visitor).apply(
std::forward<F>(foo).transfer_cref(iface));
}
template <typename F, typename V, typename ... IntTypes>
using apply_ret_type = detail::common_or_void_t<decltype(
apply_as_type<IntTypes>(std::declval<F>(), std::declval<V>()))...>;
public:
struct FreeFuncImpl // Easier than friend-ing three templates.
{
template <typename F, typename V>
static auto apply_foo_visitor(F && foo, V && visitor)
-> apply_ret_type<F, V, std::int8_t, std::uint8_t /* ... */>
{
if (type_is<std::int8_t>())
return apply_as_type<std::int8_t>(
std::forward<F>(foo), std::forward<V>(visitor));
else if (type_is<std::uint8_t>())
return apply_as_type<std::uint8_t>(
std::forward<F>(foo), std::forward<V>(visitor));
/* ... */
}
}
};
template<typename V>
decltype(auto) apply_foo_visitor(const Foo & f, V && visitor)
{
return Foo::FreeFuncImpl::apply_foo_visitor(f, std::forward<V>(visitor));
}
template<typename V>
decltype(auto) apply_foo_visitor(Foo & f, V && visitor)
{
return Foo::FreeFuncImpl::apply_foo_visitor(f, std::forward<V>(visitor));
}
template<typename V>
decltype(auto) apply_foo_visitor(Foo && f, V && visitor)
{
return Foo::FreeFuncImpl::apply_foo_visitor(std::move(f), std::forward<V>(visitor));
}
推荐阅读
- system.reactive - 重新实例化对象激射事件时可观察到的 FromEventPattern
- c# - 如何创建要在 C# 中重用的函数
- c++ - 在编译时生成函数
- android - Android资源链接失败,顺序混淆
- regex - 用于选择以“ing”结尾的单词的正则表达式,除非
- sql - 在 Teradata 中获取 EXPAND ON 的所有月份
- javascript - HTML, CSS Chatbox 分别使用百分比对齐所有元素
- reactjs - Gatsby + Netlify CMS:有什么方法可以为 src/pages/index.js 中的每个条目启用特色图像?
- php - 在我的开发服务器上保存 pdf 的文件位置
- ios - 写入前一个领域对象而不是下一个