【问题标题】:Could type traits be restricted to not accept other type traits as arguments?类型特征是否可以限制为不接受其他类型特征作为参数?
【发布时间】:2020-06-05 08:45:17
【问题描述】:

问题可能很奇怪,所以这里是一个简短的激励示例:

#include <vector>
#include <type_traits>
template <typename T>
// workaround for gcc 8.3 where volatile int is not trivially copyable
using is_tc = std::is_trivially_copyable<std::remove_cv<T>>;
// static assert passes compile, oops
static_assert(is_tc<std::vector<int>>::value);

如您所见,错误是我已将类型特征本身传递给另一个类型特征,而不是传递 ::type 或使用 std::remove_cv_t

明显的解决方案是让我不要犯错误,但我想知道 C++ 类型特征是否有办法限制它们的输入类型,以便它们不接受其他 type_traits 作为参数。 现在困难的是 type_traits 中有大量类型特征,所以 IDK 最好如何实现它。

注意:我并不是说 C++ 应该这样做,我知道防止罕见错误需要做很多工作,我只是想了解更复杂的概念设计,其中您的限制不是基于类型的语义(又名有 ++ 和 *) 但事实上类型属于大量类型(并且该集合包括您要限制的类型)。

【问题讨论】:

  • 标记一组类型的常用方法是......另一个特征!

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


【解决方案1】:

好吧,假设您在可能的情况下始终需要 ::type 作为参数,这里有一个快速的解决方法:

template<class T> concept HasType = requires { typename T::type; };
template<class T> concept HasNoType = !HasType<T>;

template<HasNoType T> using remove_cv = std::remove_cv<T>;
template<HasNoType T> using remove_cv_t = typename remove_cv<T>::type;

除了修补 STL 标头或子类化 STL 类型(并非总是允许)之外,您无法重新定义预定义的内容。

您的限制不是基于类型的语义(也就是具有 ++ 和 *),而是基于类型属于大量类型这一事实

无论如何,您都需要一个谓词来指定这个集合(给定 S 的运算符 ∊S)。例如,has ++ 和其他谓词一样好。

谓词可以用更多级别的间接和一些样板来细化,比如说

template<class T> struct not_a_type_trait =
        std::integral_constant<bool, HasNoType<T>> {};
template<class T> inline constexpr not_a_type_trait_v = not_a_type_trait<T>::value;
template<class T> concept NotATrait = not_a_type_trait_v<T>;

struct AnArg { using type = void; };
template<> struct not_a_type_trait<AnArg>: std::true_type {};
    // now can be an arg to remove_cv

或者,在这种特殊情况下,您可以简单地将所有 STL 特征列入黑名单,但这将是一个非常大的谓词,需要随着每个标准版本的更新而更新。

【讨论】:

  • 问题是我的一些类型可能有::type typedef,即struct S {int x;整数y;使用类型 = int; };
  • 我的回答很复杂。 :)
【解决方案2】:

概念:是在std命名空间中声明的TransformationTrait

我想知道 C++ 类型特征是否有办法限制它们的输入类型,以便它们不接受其他 type_traits

由于元函数特征实际上是类型本身(这也是您问题的根源),我们可以利用这一点并为 T 构建一个概念,以了解 Argument-Dependent Lookup (ADL) 是否可以找到一个 通过 ADL 对 T 类型的对象(在未评估的上下文中)进行更小的 STL 函数选择集,其中 T 可能是元函数特征;本质上是一种基于 ADL(可能很脆弱 - 见下文)的机制来查询给定类型 T 是否在 std 命名空间中定义,而不是查询 T 是否恰好是众多方法之一std 命名空间中定义的特征类型。

如果我们将它与TransformationTrait requirement 结合起来:

C++ 命名要求:TransformationTrait

TransformationTrait 是一个类模板,它提供其模板类型参数的转换。

要求

  • 采用一个模板类型参数(额外的模板参数是可选的并且是允许的)
  • 转换后的类型是一个可公开访问的嵌套类型,名为type

我们可以为 T 类型构造一个通用概念,作为 std 命名空间中的转换特征。但是请注意,如果由于某种原因给定项目开始声明重载来自 STL 的函数名称的函数,那么在这种意义上依赖 ADL 可能会有些脆弱。在可能的 ADL 查找的概念中扩展较小的 STL 函数选择集将使从非std-implementor 的角度更难打破。

例如定义几个概念如下:

namespace traits_concepts {

template <typename T>
concept FindsStlFunctionByAdlLookupOnT = requires(T t) {
  // Rely on std::as_const being found by ADL on t, i.e.
  // for t being an object of a type in namespace std.
  as_const(t);

  // If we are worried that a user may define an as_const
  // function in another (reachable/found by lookup)
  // namespace, expand the requirement with additional
  // STL functions (that can be found via ADL).
  move(t);
  // ...

  // Remember to add the appropriate includes.
};

template <typename T>
concept IsTransformationTrait = requires {
  // REQ: The transformed type is a publicly accessible
  // nested type named type.
  typename T::type;
};

template <typename T>
concept IsStlTransformationTrait =
    IsTransformationTrait<T> && FindsStlFunctionByAdlLookupOnT<T>;

template <typename T>
concept IsNotStlTransformationTrait = !IsStlTransformationTrait<T>;

}  // namespace traits_concepts

应用为:

namespace not_std {

template <traits_concepts::IsNotStlTransformationTrait T>
struct NotAnStlTrait {
  using type = T;
};

struct Foo {};

};  // namespace not_std

// Is an STL transformation trait
static_assert(
    traits_concepts::IsStlTransformationTrait<std::remove_cv<const int>>);
// Is not an STL transformation trait.
static_assert(
    !traits_concepts::IsStlTransformationTrait<std::remove_cv_t<const int>>);
static_assert(
    !traits_concepts::IsStlTransformationTrait<not_std::NotAnStlTrait<int>>);
static_assert(!traits_concepts::IsStlTransformationTrait<not_std::Foo>);

int main() {}

并且,对于添加到 std 的自定义特征(现在假设我们是编译器供应商;将名称添加到 std 命名空间是 UB):

namespace std {

// Assume we are a compiler vendor.
// (Adding names to the std namespace is UB).
template <traits_concepts::IsNotStlTransformationTrait T>
struct custom_stl_trait {
  using type = T;
};

};  // namespace std

static_assert(
    traits_concepts::IsStlTransformationTrait<std::custom_stl_trait<int>>);
static_assert(!traits_concepts::IsStlTransformationTrait<
              std::custom_stl_trait<int>::type>);

int main() {}

DEMO.

【讨论】:

  • 你确定 std:: 中没有容器/其他类型没有 ::type 成员吗?
  • @NoSenseEtAl 容器只有更详细的类型定义(value_type 等等)。 Afaik,除了转换特征(包括std::enable_if)之外,只有std::tuple_elementstd::variant_alternative 具有type 成员类型定义。但是,这些“底层变体类型”访问器与转换特征具有相似的语义,并且很可能底层变体类型(typedefed type)应该只在特征上下文中使用。
  • @NoSenseEtAl 对此有何其他反馈?除了trait-definition-invasive change proposed by Alex Guteniev 之外,我没有看到任何其他可行的选项,也没有明确注释每个给定标准版本的所有特征。
  • 这对我来说似乎是最好的解决方案,但我必须警告你,我绝对不是 C++20 专家,所以我接受这个答案并不意味着它是最好的。这对我来说似乎是最好的答案。
  • @NoSenseEtAl Imo 这个问题的任何复杂性都不是真正的 C++20 技术,而是所有答案(包括这个)都依赖于一些脆弱的技术来检测“T is a trait ” 并且,扩展,“T 是一个 STL 特征”(type typedef 的存在 // 由 STL 命名的函数组成的概念,对于给定的类型 T,都应该找到这些函数,希望 i> 没有用户为非 STL 类型实现所有这些功能,依此类推)。如果这是一个人们真正想要的功能,那么正确的解决方案可能是编写一个进化提案,用一些特征符号来扩展 STL 特征。
【解决方案3】:

我认为如果所有特征都可以检查其他特征,例如所有特征都继承自_Trait,并对其模板参数执行 st is_base_of_v

template<class T>
struct remove_cv : private _Trait
{
    static_assert(!is_base_of_v<_Trait, T>, "Don't pass traits to traits");
    using type = T;
};

如果您想要警告而不是硬错误,那就更难了。需要使static_assert 像往常一样评估true,但实例化[[deprecated]] 类以将特征传递给特征。


另一个简单的解决方案是标记[[deprecated]] 所有需要::type::value 的特征,弃用它们以支持_t / _v。这是非标准的,但可以在一些预处理器宏下完成。或者可以通过包含列出这些弃用的标题来提供此弃用。

【讨论】:

    【解决方案4】:

    标记一组类型的常用方法是使用 trait ;-)

    template <class T>
    struct is_trait : std::false_type {};
    
    template <class T>
    struct is_trait <is_trait <T>> : std:: true_type {};
    
    template <class T>
    inline constexpr auto is_trait_v = is_trait:: value;
    

    【讨论】:

      猜你喜欢
      • 2013-11-21
      • 2021-07-22
      • 1970-01-01
      • 1970-01-01
      • 2021-12-28
      • 2012-05-17
      • 2018-04-11
      • 1970-01-01
      • 2020-12-31
      相关资源
      最近更新 更多