【问题标题】:Disable class specialization using concepts使用概念禁用类专业化
【发布时间】:2019-01-16 16:53:34
【问题描述】:

我正在使用概念 TS 实现我自己的 std::span 版本。我在执行these constructors 时遇到了困难:

template<class Container> constexpr span(Container& cont);
template<class Container> constexpr span(const Container& cont);

备注:这些构造函数不应参与重载决议,除非:

  • Container 不是 span 的特化,并且
  • Container 不是 array 的特化

如何使用概念来实现这一点?

【问题讨论】:

  • 你能实现一个特征模板来回答这些问题吗?您可以将特征模板转换为概念吗?这能解决你的问题吗?
  • 可能有答案here?好问题!

标签: c++ c++-concepts c++20


【解决方案1】:

首先,创建一个特征来检查特化。 arrayspan 在接受类型参数和非类型参数的意义上看起来是一样的:

template <typename T, template <typename, auto> class Z>
struct is_specialization : std::false_type { };
template <typename A, auto V, template <typename, auto> class Z>
struct is_specialization<Z<A,V>, Z> : std::true_type { };

template <typename T, template <typename, auto> class Z>
inline constexpr bool is_specialization_v = is_specialization<T, Z>::value;

然后我们可以从中建立一个概念:

// the last bullet point
template <typename T, typename E>
concept ValidForElement =
    ConvertibleTo<std::remove_pointer_t<T>(*)[], E(*)[]>;

template <typename T, typename E>
concept AllowedContainer =
    // not a specialization of span (note: requires forward declaration of span)
    !is_specialization_v<std::remove_cv_t<T>, std::span>
    // not a specialization of array
    && !is_specialization_v<std::remove_cv_t<T>, std::array>
    // not a raw array
    && !std::is_array_v<std::remove_cv_t<T>>
    && requires (T cont) {
        // data(cont) is well-formed and has a valid type
        { data(cont); } -> ValidForElement<E>
        // size(cont) is well-formed
        { size(cont); }
    };

你会使用喜欢的:

template <typename Element, std::ptrdiff_t Extent = -1>
struct span {
    template <typename C> requires AllowedContainer<C, Element>
    span(C&);
    template <typename C> requires AllowedContainer<C const, Element>
    span(C const&);
};

那里的const-ness 要求阻止了漂亮的 partial-concept-id 语法,但我想我们可以添加另一个概念:

template <typename T, typename E>
concept ConstAllowedContainer = AllowedContainer<T const, E>;

template <typename Element, std::ptrdiff_t Extent = -1>
struct span {
    template <AllowedContainer<E> C>      span(C&);
    template <ConstAllowedContainer<E> C> span(C const&);
};

不确定这里是否有更聪明的方法。


但实际上这整个对构造函数的事情可能是一个错误,你想做一个转发引用:

template <typename Element, std::ptrdiff_t Extent = -1>
struct span {
    template <AllowedContainer<E> C>
    span(C&&);
};

最后一种方法需要对概念进行一些调整(所有remove_cv_t 都应该变成remove_cvref_t)。

【讨论】:

  • 是否可以使用可变参数泛化is_specialization
  • @Lyberta 问题是您有不同的 种类 模板参数:类型参数和非类型参数。没有“任何”可以放在那里的东西。这正是“anykind”参数非常有用的情况。
【解决方案2】:

您可以使用类型特征来检查某个类型是否是spanstd::array 的特化。这对我有用:

#include <type_traits>

template<typename, std::ptrdiff_t> class span;

template <typename T>
struct is_array : std::false_type { };
template <typename T, size_t N>
struct is_array<std::array<T, N>> : std::true_type { };

template <typename T>
struct is_span : std::false_type { };
template <typename T, std::ptrdiff_t P>
struct is_span<span<T, P>> : std::true_type { };

template <typename T>
concept bool NotSpanNotArray = !is_array<T>::value && !is_span<T>::value;

template<typename, std::ptrdiff_t> class span {
public:
  template<NotSpanNotArray T> constexpr span(T& cont);
  // template<NotSpanNotArray T> constexpr span(const T& cont);
};

工作演示:https://wandbox.org/permlink/M0n60U8Hl4mpacuI

只是我不能 100% 确定这样的解决方案是否满足当且仅当要求时才参与重载解决。一些语言律师可能会澄清这一点。


更新

我刚刚意识到std::is_array 仅适用于“普通”数组,不适用于std::array。因此,我还添加了一个自定义的 is_array 类型特征。

【讨论】:

  • “当且仅当”是由另一个用户添加的,我已经恢复了“除非”的官方措辞。
  • 不是通过将其包装在enable_if 中以利用 SFINAE 来完成重载解析吗?
  • @Lyberta 抱歉,我正试图摆脱三重否定。我认为正确的形式应该是“仅当”,但我相信意图是明确的。
  • @davidbak 没有概念,这是正确的语法,但会向后限制(需要是NotSpanNorArray
  • @Barry 已更新,谢谢。我现在只是在我的手机上,也会更新演示。
【解决方案3】:

你误用了概念。在概念世界中,span 构造函数将如下所示,使用 concept-ts 语法:

struct span{
   span(const ContiguousRange&);
   span(ContiguousRange&);
   span(const span&) =default;
   span(span&) =default;
   };

或使用 c++20:

struct span{
   span(const ContiguousRange auto&);
   span(ContiguousRange auto&);
   span(const span&) =default;
   span(span&) =default;
   };

概念是为了简化抽象。因此,如果您的界面在使用概念时变得更加复杂,那么您就错过了抽象。这里的抽象是ContiguousRange(感谢@Lyberta)。

【讨论】:

  • span 需要连续内存。 AFAIK Range 不保证。
  • @Lyberta 谢谢你,所以我更正并改用 ContiguousRange ! eel.is/c++draft/range.req#range.refinements-2
  • 嗯,实际上,这需要实现范围,这是一项巨大的工作。我只实现了我的跨度,因为它还没有在我使用的 libstdc++ 中。也许使用范围是一个更好的接口,那么应该提出标准化。
  • @Lyberta 在这种情况下,您尝试实施的概念检查将无用。原因是en.cppreference.com/w/cpp/container/span/span 中描述的span 构造函数列表已经为数组特化和跨度特化提出了重载。实施概念检查几乎没有用处。我说几乎是因为我认为他们是这个列表中的设计或 cpp-reference 错误。只需添加一个span(span&lt;U,N&gt;&amp;) 构造函数和一个span(span&amp;) 构造函数即可完成。
  • 我实现了标准中的所有 10 个构造函数。
猜你喜欢
  • 2021-09-04
  • 1970-01-01
  • 1970-01-01
  • 2021-02-23
  • 1970-01-01
  • 2022-01-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多