为什么我们不能专门化概念?

Why can't we specialize concepts?

适用于 类 的语法不适用于概念:

template <class Type>
concept C = requires(Type t) {
    // ...
};


template <class Type>
concept C<Type*> = requires(Type t) {
    // ...
};

MSVC 表示“专业化”行:error C7606: 'C': concept cannot be explicitly instantiated, explicitly specialized or partially specialized.

为什么概念不能专业化?有理论上的原因吗?

因为它会破坏约束规范化和包含规则。

就目前而言,每个 concept 都只有一个定义。因此,概念之间的关系是已知的并且固定。考虑以下因素:

template<typename T>
concept A = atomic_constraint_a<T>;

template<typename T>
concept B = atomic_constraint_a<T> && atomic_constraint_b<T>;

根据 C++20 的当前规则,B 包含 A。这是因为,在约束规范化之后,B 包含了 A.

的所有原子约束

如果我们允许概念专门化,那么 BA 之间的关系现在取决于提供给这些概念的 参数 B<T> 可能包含 A<T> 一些 T 但不是其他 T

但这不是我们使用概念的方式。如果我试图编写一个比另一个模板“更受限制”的模板,那么唯一的方法就是使用一组已知的 well-defined 概念。这些定义 不能 取决于这些概念的参数。

编译器应该能够计算出一个受约束的模板是否比另一个没有 有任何模板参数 更受约束。这很重要,因为让一个模板比另一个模板“更受约束”是使用概念和约束的一个关键特征。

具有讽刺意味的是,允许概念专门化会破坏(约束)其他模板 的专门化。或者至少,这会使实施变得非常困难。

除了 Nicol Bolas 的精彩回答:

概念有点特殊,因为它们的行为不像其他模板化的东西:

13.7.9 Concept definitions

(5) A concept is not instantiated ([temp.spec]).
[Note 1: A concept-id ([temp.names]) is evaluated as an expression. A concept cannot be explicitly instantiated ([temp.explicit]), explicitly specialized ([temp.expl.spec]), or partially specialized ([temp.spec.partial]). — end note]

由于概念无法实例化,它们也无法专门化。

鉴于模拟专业化很容易,我不确定为什么标准决定不让它们专业化。


虽然您不能直接专门化概念,但有很多方法可以解决这个问题。

您可以在概念中使用任何类型的常量表达式 - 因此您可以使用模板化变量(可以专门化)并将其包装到一个概念中 - 标准本身的很多部分都是这样做的概念也是如此,例如std::is_intergral:

template<class T> struct is_integral;

// is_integral is specialized for integral types to have value == true
// and all others are value == false

template<class T>
inline constexpr bool is_integral_v = is_­integral<T>::value;

template<class T>
concept integral = is_­integral_­v<T>;

所以你可以很容易地写出一个像这样的专业化的概念:godbolt example

struct Foo{};
struct Bar{};

template<class T>
constexpr inline bool is_addable_v = requires(T t) {
    { t + t } -> std::same_as<T>;
};

// Specializations (could also use other requires clauses here)
template<>
constexpr inline bool is_addable_v<Foo> = true;

template<class T>
constexpr inline bool is_addable_v<T&&> = true;

template<class T>
concept is_addable = is_addable_v<T>; 

int main() {
    static_assert(is_addable<int>);
    static_assert(is_addable<Foo>);
    static_assert(!is_addable<Bar>);
    static_assert(is_addable<Bar&&>);
}

或者使用 class:

template<class T>
struct is_addable_v : std::true_type {};

template<>
struct is_addable_v<struct FooBar> : std::false_type {};

template<class T>
concept is_addable = is_addable_v<T>::value; 

甚至是一个 constexpr lambda:godbolt example

// pointers must add to int
// everything else must add to double
template<class T>
concept is_special_addable = ([](){
    if constexpr(std::is_pointer_v<T>)
        return requires(std::remove_pointer_t<T> t) {
            { t + t } -> std::same_as<int>;
        };
    else
        return requires(T t) {
            { t + t } -> std::same_as<double>;
        };
})(); 

int main() {
    static_assert(is_special_addable<double>);
    static_assert(is_special_addable<int*>);
    static_assert(!is_special_addable<double*>);
    static_assert(!is_special_addable<int>);
}

因此,虽然概念不能单独专门化,但很容易通过现有的语言功能实现相同的效果。

在这种情况下的专业化开辟了一大堆蠕虫。我们用模板专业化打开了这个包。模板专业化是使通用图灵完备的模板语言的主要部分。是的,您可以在模板中编程。你不应该,但你可以。 Boost 有一个名为 Boost.MPL 的库,它充满了聪明的东西,比如在编译时运行的“无序映射”,而不是 运行 时间。

所以我们必须小心地限制它。简单的案例可能有效,但复杂的案例将不得不被禁止。当然,任何能够远程创建递归约束的东西都必须仔细观察。确实,考虑一个概念:

template <typename T>
concept hailstone = false;

template <int i>
concept hailstone<std::integral_constant<int, i> =
    hailstone<2 * i> || (i % 2 == 1 && hailstone<3*i - 1>);

template <>
concept hailstone<std::integral_constant<int, 0> = true;

那么,std::integral_constant<int, 27>是冰雹吗?这可能需要一段时间。我选择的示例基于 Collatz Conjecture 中的冰雹数字。确定任何给定的数字是否是冰雹是非常困难的(尽管,据我们所知,每个 数字都是冰雹数字)。

现在将 integral_constant 替换为可以实现任意精度的巧妙结构。现在我们有麻烦了!

现在我们可以仔细切分这个问题的元素并将它们标记为可行。规范社区不参与该业务。我们在 C++20 中知道的概念被昵称为 concepts-lite,因为它实际上是一个从未进入 C++11 的概念库的大幅简化版本。该库有效地实现了 Description Logic、class 已知可判定的逻辑。这很重要,因为计算机必须 运行 通过所有必要的计算,我们不希望他们花费无限多的时间。 Concepts 源自于此,因此遵循相同的规则。而且,如果您查看描述逻辑,您证明许多陈述的方式涉及首先枚举所有 named 概念的列表。一旦你列举了这一点,就很容易证明你可以在有限的时间内解决任何概念需求。

正如 Nicol Bolas 在他的回答中指出的那样,概念的目的并不是成为某种聪明的图灵完备系统。这是为了提供更好的错误信息。因此,虽然人们可能能够在精心选择的路径中巧妙地滑入 一些 专业化,但没有动力这样做。