首页 > 解决方案 > 对于采用可复制和可移动类的访问者,应该有多少重载?

问题描述

我有一个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());

在库中,我可以编写一个访问者的重载版本

我的问题是我应该提供哪些。我想给用户尽可能多的自由来编写他们的访问者,以便他们可以在需要时对 Foo 强制执行 constness,或者如果他们不这样做则允许对其进行修改,我也希望他们复制它如果他们愿意,或者如果他们不喜欢就移动它。但是,我不想在我的实现中编写冗余或不必要的代码,并且我想确保编译器能够处理客户端可能提出的访问者函数的任何合理使用。

在这方面是否有任何最佳实践?例如,对于我来说,编写一个接受左值引用的访问者版本和另一个接受右值引用的访问者版本,没有限定为 const 就足够了吗?

以下两个问题的答案:

防止非常量左值解析为右值引用而不是 const 左值引用

与右值交换

建议最好在问题描述的场景中编写一个带有右值引用的版本,并且这将是通用的,足以正确处理左值。在我的情况下是否也是如此,我只想在适当的时候移动,否则通过 const 或非 const 引用访问?

标签: c++

解决方案


所以看看你的潜在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));
}

推荐阅读