【问题标题】:Why can't we specialize concepts?为什么我们不能专门化概念?
【发布时间】:2022-01-24 03:37:33
【问题描述】:

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

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

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

【问题讨论】:

  • 这似乎会让这个概念的用户感到困难,他们依靠这个来使用他们拥有的类型。现在他们还必须专门化或类似,以便使类型的使用遵循这个概念。你有什么特别激励人心的例子吗?
  • 激励示例(这是用法):IsCallableWithSignature&lt;MyType, void(int, bool)&gt;。实现这一点的一个直接方法是专门化这个概念。使用类的解决方法:link.

标签: c++ c++20 template-specialization c++-concepts


【解决方案1】:

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

就目前而言,每个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&lt;T&gt; 可能包含 A&lt;T&gt; 一些 Ts 但不能包含其他 Ts。

但这不是我们使用概念的方式。如果我正在尝试编写一个比另一个模板“更受约束”的模板,那么唯一的方法就是使用一组已知的、定义明确的概念。而这些定义不能依赖于这些概念的参数。

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

具有讽刺意味的是,允许对概念进行特化会破坏(受约束的)其他模板的特化。或者至少,它会让它很难实现。

【讨论】:

  • 我相信您可以通过将概念转发到专门的模板来作弊;这实际上是一个原子约束。我可以看到具有可以专门化(或以其他方式匹配模式)的“特殊原子”约束的效用,或者至少比我们拥有的特征类专门化东西更好的语法。语法糖,但这很重要。
  • @Yakk-AdamNevraumont:但是通过将其变成原子约束黑匣子,您就失去了概念本身的任何意义。例如,如果您对random_access_iterator 执行此操作,它将不再包含bidirectional_iterator。我们应该为的想法提供语法糖,而不是你不应该做的事情。
  • 假设当前的归并规则是这样的:B 归并A iff X。我建议启用专业化并将规则修改为:B 包含 A 如果它们都没有专业化和 X。可以吗?
  • @Dr.Gut:这到底有什么意义?如果你不能保证包容,那么你就不能依赖它。我不应该能够编写一个期望存在包含关系的模板,然后让你来用专业化来打破它。只需将其设为 constexpr inline static bool 变量即可,其中不存在包含而专门化存在。
  • @NicolBolas:这让我想到了。这是个坏主意。谢谢。
【解决方案2】:

除了 Nicol Bolas 的精彩回答:

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

13.7.9 Concept definitions

(5) 未实例化概念 ([temp.spec])。
[注意1:concept-id ([temp.names]) 被评估为表达式。概念不能显式实例化 ([temp.explicit])、显式特化 ([temp.expl.spec]) 或部分特化 ([temp.spec.partial])。 — 尾注]

由于概念不能被实例化,它们也不能被特化。

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


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

您可以在概念中使用任何类型的常量表达式 - 因此您可以使用模板化变量(可以专门化)并将其包装成一个概念 - 该标准针对其自身的许多概念执行此操作好吧,例如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&&>);
}

或者通过使用一个类:

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>);
}

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

【讨论】:

    【解决方案3】:

    在这种情况下的专业化会打开一袋蠕虫。我们用模板专业化打开过这个包。模板专业化是使模板语言总体上图灵完备的主要部分。是的,您可以在模板中编程。你不应该,但你可以。 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&lt;int, 27&gt; 是冰雹吗?这可能需要一段时间。我选择的示例基于来自Collatz Conjecture 的冰雹数字。确定任何给定的数字是否是冰雹非常困难(尽管据我们所知,每个数字都是冰雹数字)。

    现在用一个可以做任意精度的聪明的结构替换integral_constant。现在我们有麻烦了!

    现在我们可以小心地切掉这个问题的元素并将它们标记为可行。规范社区不从事该业务。我们在 C++20 中所知道的概念被称为概念精简版,因为它实际上是概念库的一个大大简化的版本,从未进入 C++11。该库有效地实现了Description Logic,这是一类已知可判定的逻辑。这很重要,因为计算机必须运行所有必要的计算,我们不希望它们花费无限的时间。概念是从这里派生出来的,所以它遵循相同的规则。而且,如果您查看描述逻辑,您证明许多陈述的方式涉及首先枚举所有命名概念的列表。一旦你列举了这一点,证明你可以在有限的时间内解决任何概念需求是微不足道的。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-02-23
      • 2021-06-12
      • 1970-01-01
      • 2018-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多