类型特征是否可以限制为不接受其他类型特征作为参数?

Could type traits be restricted to not accept other type traits as arguments?

问题可能很奇怪,所以这里有一个简短的动机示例:

#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++ 应该这样做,我知道要防止罕见的错误需要做很多工作,我只是想了解更复杂的概念设计,其中您的限制不是基于类型的语义(又名有 ++ 和 *)但事实上类型属于一大组类型(并且该组包括您限制的类型)。

好吧,假设您总是尽可能需要一个 ::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 headers 或子类化 STL 类型(这并不总是被允许)之外,您不能重新定义预定义的内容。

your restriction is not based on semantics of types(aka has ++ and *) but on the fact that types belong to a huge set of types

无论如何,您都需要一个谓词来指定此集合(运算符 ∊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 的特征列入黑名单,但这将是一个非常庞大的谓词,要随每个标准修订版一起更新。

我认为如果所有特征都检查其他特征是可能的,比如所有特征都继承自 _Trait,并在其模板参数上执行 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]] class 以传递给 trait.


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

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

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;

概念:是在 std 命名空间

中声明的 TransformationTrait

I wonder if is there a way C++ type traits could restrict their input types so that they do not accept other type_traits

由于元函数特征实际上是类型本身(这也是您问题的根源),我们可以利用这一点并为 T 构造一个概念,以确定参数依赖查找 (ADL) 是否可以找到更小的 select 集 的 STL 函数通过 ADL 在类型 T 的对象上(在非评估上下文中),其中 T 可能可能是元功能特征;本质上是一种基于 ADL(可能很脆弱 - 见下文)的机制来查询给定类型 T 是否在 std 命名空间中定义,而不是查询是否 [=14= 的冗长方法] 正是 std 命名空间中定义的众多特征类型之一。

如果我们将其与 TransformationTrait requirement 结合使用:

C++ named requirements: TransformationTrait

A TransformationTrait is a class template that provides a transformation of its template type parameter.

Requirements:

  • Takes one template type parameter (additional template parameters are optional and allowed)
  • The transformed type is a publicly accessible nested type named type

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

例如定义几个概念如下:

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.