约束和要求详解

380 阅读3分钟

约束和要求详解

要指定模板参数的需求,您需要约束,这些约束是布尔表达式,在编译时计算为true或false,以决定是否实例化和编译模板。

约束可以是:

  • 一个临时布尔编译时表达式(语法受限,因此可能需要括号以避免歧义)
  • 需要指定所需操作和类型的表达式
  • 一个概念(之前定义,通过使用关键字概念)

约束的基本应用是require子句,它使用关键字require和约束来限制模板的可用性。所有约束也可以用于任何可以使用布尔表达式的地方(特别是作为if constexpr条件)。

要组合多个约束,可以使用&&。例如:

template<typename T>

requires (sizeof(T)>4) 			//ud-hoc Boolean expression 

	&& requires { typename T::value_type;} 	//requires expression 

	&& std::input_iterator<T> 		//concept 

约束的顺序并不重要。也可以使用||表示替代约束:

template<typename T>

requires std::integral<T> || std::floating_point<T> T power(T b,Tp);

很少需要指定替代约束,也不应该太随意,因为在require子句中过度使用||操作符可能会占用编译资源(即,使编译明显变慢)。

单个约束也可以涉及多个模板参数:例如

template<typename T, typename U>

requires std::convertible_to<T, U> auto f(T x,U y){

...

}

这样,约束可以在类型参数之间施加关系。

特殊布尔表达式

为模板制定约束的第一种方法是使用转换为true或false的编译时表达式。

  • 类型谓词,例如类型特征
  • 编译时变量(用constexpr或constinit const定义)
  • 编译时函数(使用 constexpr 或 consteval 定义)

让我们看一些限制模板可用性的特殊布尔表达式的例子

  • 仅当int和long的大小不同时可用

    template<typename T>
    
    requires (sizeof(int) != sizeof(long))
    
  • 仅当sizeof (T)不太大时可用

    template<typename T>
    
    requires (sizeof(T)<=64) ...
    
  • 仅当非类型模板参数Sz大于零时可用

    template<typename T, std::size_t Sz>
    
    requires (Sz > 0) ..·
    
    
  • 仅适用于原始指针和 nullptr:

    template<typename T>
    
    requires (std::is_pointer_v<T> || std::same_as<T, std::nu111ptr_t>) ...
    
    std::same_as is a new standard concept. You could also use the type trait std::is_same<>: template<typename T>
    
    requires (std::is_pointer_v<T> || std::is_same_v<T, std::nullptr_t>) ...
    
    
  • 仅当参数不能作为字符串使用时可用

    template<typename T>
    
    requires (!std::convertible_to<T, std::string>)
    
    
    std::convertible_to is a new standard concept.
    

    你也可以使用类型:

    
       trait std::is_convertible<>:
    
       template<typename T>
    
       requires (!std::is_convertible_v<T, std::string>) ...
    
  • 仅当实参是指向整型值的指针(或类指针对象)时可用

    template<typename T>
    
    requires std::integral<std: :remove_reference_t<decltype(*std::declval<T>())>>
    

    注意,操作符*通常产生一个引用,而不是整型。因此,我们采取以下措施

    -假设我们有一个类型为T的对象:std::declval()

    • 为它调用运算符*:*
    • 询问它的类型:decltype()
    • 删除引用:std::remove_reference_v<>
    • 检查整型:std::integral

    std:: integral 是一个新的标准概念。

    你也可以使用类型trait std::is integral<>。这个约束也可以通过std::optional<int>来满足。

  • 只有当非类型模板参数Min和Max的最大公约数大于1时才可用

        template<typename T>
    
        constexpr bool gcd(T a,T b); // 最大公约数  
    
        template<typename T, int Min, int Max>
    
        requires (gcd(Min,Max)> 1)
    
        // 如果 u GCD 大于 1 ...
    
  • 禁用模板(临时)

     template<typename T>
    
    requires false 
    

通常,整个表达式需要用括号括起来。

唯一的例外是使用标识符、::和<…>(可选与&&和II组合)

        requires std::convertible_to<T,int>

         &&

        (!std: :convertible_to<int,T>) 

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情