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

7

可能问题有点奇怪,这里有一个简短的激励例子:

#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中有大量的类型特征,因此我不知道最佳实现方法应该是什么。

注意:我并不是说C++应该这样做,我知道防止罕见错误需要很多工作,我只是想学习更复杂的概念设计,其中你的限制不是基于类型的语义(即具有++和*),而是基于类型属于巨大的类型集合(并且该集合包括你所限制的类型)。


标记一组类型的常规方法是......使用另一个特征! - Oliv
4个回答

4

假设你总是需要在可能的情况下将 ::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; int y; using type = int; }; - NoSenseEtAl
1
我把我的回答搞得很复杂。 :) - bipll

2

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

我在想,C++类型特征是否有一种方式可以限制它们的输入类型,使其不接受其他类型特征。

由于元函数特征本身就是类型(这也是问题的根源),我们可以利用这一点,并为T构造一个概念,即在非评估上下文中,Argument-Dependent Lookup(ADL)能否找到一组更小的STL函数集,通过ADL在类型为T的对象上查找(可能是脆弱的-见下文),其中T可能潜在地是元函数特征。本质上,这是一种基于ADL的机制,用于查询给定类型T是否定义在std命名空间中,而不是冗长的查询是否T恰好是在std命名空间中定义的众多特征类型之一。

如果将此与TransformationTrait要求相结合:

C++命名要求:TransformationTrait

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

要求

  • 接受一个模板类型参数(其他模板参数是可选的且允许的)
  • 转换后的类型是一个名为type的公共可访问嵌套类型。
我们可以构造一个通用概念,用于表示T类型在std命名空间中是否为TransformationTrait。但请注意,在这种意义上依赖ADL可能有些脆弱,如果某个项目出于某种原因开始声明从STL重载函数名称的函数,则会使情况变得更加复杂。扩展概念中用于可能的ADL查找的较小选择集合的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命名空间,则对于自定义特征:

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() {}

演示.


你确定标准库中没有容器/其他类型没有 ::type 成员吗? - NoSenseEtAl
@NoSenseEtAl 容器只有更冗长的typedef(例如value_type)。据我所知,除了转换特性(包括 std::enable_if)之外,只有std::tuple_elementstd::variant_alternative拥有type成员typedef。这些“底层变量类型”访问器具有类似于转换特性的语义,很可能应该仅在特性上下文中使用底层变量类型(typedefed type)。 - dfrib
@NoSenseEtAl,对此还有其他反馈吗?我没有看到其他可行的选择,除了Alex Guteniev提出的侵入性更改trait-definition-invasive change proposed by Alex Guteniev,或者明确注释每个给定标准版本的所有特征。 - dfrib
这对我来说看起来是最好的解决方案,但我必须警告你,我绝对不是C++20专家,所以我接受这个答案并不意味着它是最好的。它只是在我看来看起来是最好的答案。 - NoSenseEtAl
@NoSenseEtAl 我认为这个问题的复杂性并不是真正的C++20技术,而是所有答案(包括这个答案)都依赖于一些脆弱的技术来检测“T是一个特征”,并且扩展了“T是一个STL特征”(存在type typedef // STL命名函数的组合概念,对于给定类型T应该找到所有这些函数,希望没有用户为非STL类型实现所有这些函数,等等)。如果这是一个人们真正想要的功能,正确的解决方案可能是编写一个演化提案,以扩展STL特征与一些特征符号。 - dfrib

1

我认为如果所有特征都检查其他特征,比如所有特征都从_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,但对于传递给 trait 的 trait 实例化 [[deprecated]] 类。
另一个简单的解决方案是标记所有需要::type::value的特征,用_t/_v取代它们,并将它们标记为[[deprecated]]。虽然这不是标准做法,但可以在某些预处理器宏下完成。或者,通过包含一个列出这些废弃特征的头文件来提供此废弃通知。

0
通常标记一组类型的方法是使用特质;-)
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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接