概念的偏序如何解决约束重载?

Rex*_*uan 5 c++ templates concept

根据cppreference,包含约束的部分排序用于确定“模板模板参数的最佳匹配”。并且在示例部分中,选择了“更受约束”的选项,即更强/更严格的条件。但在实践中,当函数概念约束明确排序并且特定调用可以清楚地区分最强选项时,我发现了一些令人困惑的行为。

我正在 gcc 版本 10.2.0 (Homebrew GCC 10.2.0) 上测试以下内容: 使用:

template <typename... Ts>
void foo (Ts... ts) // option 1
{
    (cout << ... << ts) << endl;
}

template <typename... Ts> requires (sizeof...(Ts)<=3)
void foo (Ts... ts) // option 2
{
    (cout << ... << ts) << endl;
}
Run Code Online (Sandbox Code Playgroud)

的调用foo(1,2)将选择选项 2,因为它的约束明显强于选项 1。另一方面,这显然会导致歧义:

template <typename... Ts>
void foo (Ts... ts) // option 1
{
    (cout << ... << ts) << endl;
}

template <typename... Ts> requires (sizeof...(Ts)<=3)
void foo (Ts... ts) // option 2
{
    (cout << ... << ts) << endl;
}

template <typename... Ts> requires (same_as<Ts, int> && ...)
void foo (Ts... ts) // option 3
{
    (cout << ... << ts) << endl;
}
Run Code Online (Sandbox Code Playgroud)

因为调用foo(1,2)无法决定是选择选项 2 还是选项 3,因为它们没有可比性。现在,如果我理解正确的话,请添加一个连词,例如:

template <typename... Ts>
void foo (Ts... ts) // option 1
{
    (cout << ... << ts) << endl;
}

template <typename... Ts> requires (sizeof...(Ts)<=3)
void foo (Ts... ts) // option 2
{
    (cout << ... << ts) << endl;
}

template <typename... Ts> requires (same_as<Ts, int> && ...)
void foo (Ts... ts) // option 3
{
    (cout << ... << ts) << endl;
}

template <typename... Ts> requires (sizeof...(Ts)<=3) && (same_as<Ts, int> && ...)
void foo (Ts... ts) // option 4
{
    (cout << ... << ts) << endl;
}
Run Code Online (Sandbox Code Playgroud)

调用foo(1,2)应该使用选项 4 来解析,但我的编译器却另有说法:

In function 'int main()':
error: call of overloaded 'foo(int, int)' is ambiguous
      |     foo(1,2);
      |            ^
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
      | void foo (Ts... ts) // option 1
      |      ^~~
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
      | void foo (Ts... ts) // option 2
      |      ^~~
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
      | void foo (Ts... ts) // option 3
      |      ^~~
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
      | void foo (Ts... ts) // option 4
Run Code Online (Sandbox Code Playgroud)

这是为什么?如果这是不可避免的,有什么办法可以解决这个问题吗?

Hol*_*Cat 2

当编译器检查一组需求是否比另一组需求受到更多限制时,它会递归地扩展概念。&&它还理解, ||,的含义( )(因此连接/析取的顺序并不重要,多余的( )并不重要,等等),甚至在概念内部也是如此。

这部分非常直观。不直观的是,两个要求在词法上相同并不足以被认为是等效的。在概念扩展之前,它们实际上必须是源代码中同一位置的相同表达式。这就要求它们源自同一个概念。

另一个不直观的部分是&&||失去了折叠表达式中的特殊含义,因此要使两个折叠表达式被认为是等效的(无论它们使用&&||或其他东西),它们也必须在概念被定义之前位于源代码中的相同位置。扩大了。

知道了这一点,解决方案就是抽象sizeof...(Ts) <= 3(same_as<Ts, int> && ...)概念。

有很多方法可以做到这一点。您可以根据需要笼统或具体:

  1. template <typename ...P>
    concept at_most_3 = sizeof...(P) <= 3;
    
    template <typename ...P>
    concept all_ints = (std::same_as<P, int> && ...);
    
    Run Code Online (Sandbox Code Playgroud)

    用法:requires at_most_3<Ts...> && all_ints<Ts...>

  2. template <auto A, auto B>
    concept less_eq = A <= B;
    
    template <typename T, typename ...P>
    concept all_same_as = (std::same_as<T, P> && ...);
    
    Run Code Online (Sandbox Code Playgroud)

    用法:requires less_eq<sizeof...(Ts), 3> && all_same_as<int, Ts...>

即使是完全令人震惊的template <bool X> concept boolean = X;,被用作requires boolean<sizeof...(Ts) <= 3> && boolean<(std::same_as<Ts, int> && ...)>,似乎也有效!

  • 有趣的是,“boolean”概念显然使编译器使用依赖表达式等价,根据 [\[temp.over.link\]/5](https://timsong-cpp.github.io/cppwp/temp.over.link #5),而不是词汇表达同一性。尽管我认为标准中对于具体发生的情况存在一些漏洞。 (4认同)