这是一个带有 C++20 概念的 gcc 11 编译器错误,还是我做错了什么?

Is this a gcc 11 compiler bug with C++20 concepts, or am I doing something wrong?

我正在试验 C++20 概念,使用我自己本地构建的 GCC 11 源代码的克隆。我从 GCC 收到编译错误,这对我来说似乎是错误的。我已将触发诊断的代码减少到以下内容,使用 GCC 命令行对其进行编译,并在其下方生成完整的诊断输出。

为了重点,诊断输出中对我来说似乎错误的特定部分是

repro.cpp:9:13: note: the required expression ‘flequal(a, b)’ is invalid, because
    9 |     {flequal(a, b)} -> std::convertible_to<bool>;
      |      ~~~~~~~^~~~~~
repro.cpp:9:13: error: ‘flequal’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
repro.cpp:22:6: note: ‘template<class auto:1, class auto:2>  requires (Flequalable<auto:1>) && (Flequalable<auto:2>) bool flequal(const Bar<auto:1>&, const Bar<auto:2>&)’ declared here, later in the translation unit
   22 | bool flequal(Bar<Flequalable auto> const &a, Bar<Flequalable auto> const &b) {
      |      ^~~~~~~

它说候选函数在翻译单元中的声明晚于实例化点,但事实并非如此,至少按照我的代码片段中的源代码行顺序是这样。

这个构建错误是错误的,还是我在使用 C++20 概念时做错了什么?

注意代码末尾的注释中的变体,它编译时没有任何诊断。

代码:

#include <concepts>

using std::floating_point;

template< typename T >
concept Flequalable
    = floating_point<T>
    && requires(T a, T b) {
        {flequal(a, b)} -> std::convertible_to<bool>;
    };

template<floating_point T>
bool flequal( T a, T b) {
    return true;
}

template<typename T>
struct Bar {
    T t;
};

bool flequal(Bar<Flequalable auto> const &a, Bar<Flequalable auto> const &b) {
    return true;
}

bool foo() {
    Bar<double> a = {2.0};
    Bar<double> b = {3.0};
    return flequal(a, b); // Causes diagnostic
    // return flequal(a.t, b.t); // This works
}

编译命令行:

/usr/local/gcc-11-master/bin/g++-11 -c repro.cpp -o repro.o -std=c++20 -fconcepts-diagnostics-depth=5 2> diagnostic.txt

完整的 GCC 诊断:

repro.cpp: In function ‘bool foo()’:
repro.cpp:29:24: error: no matching function for call to ‘flequal(Bar<double>&, Bar<double>&)’
   29 |     return flequal(a, b); // Causes diagnostic
      |                        ^
repro.cpp:13:6: note: candidate: ‘template<class T>  requires  floating_point<T> bool flequal(T, T)’
   13 | bool flequal( T a, T b) {
      |      ^~~~~~~
repro.cpp:13:6: note:   template argument deduction/substitution failed:
repro.cpp:13:6: note: constraints not satisfied
In file included from repro.cpp:1:
/usr/local/gcc-11-master/include/c++/11.0.1/concepts: In substitution of ‘template<class T>  requires  floating_point<T> bool flequal(T, T) [with T = Bar<double>]’:
repro.cpp:29:24:   required from here
/usr/local/gcc-11-master/include/c++/11.0.1/concepts:111:13:   required for the satisfaction of ‘floating_point<T>’ [with T = Bar<double>]
/usr/local/gcc-11-master/include/c++/11.0.1/concepts:111:30: note: the expression ‘is_floating_point_v<_Tp> [with _Tp = Bar<double>]’ evaluated to ‘false’
  111 |     concept floating_point = is_floating_point_v<_Tp>;
      |                              ^~~~~~~~~~~~~~~~~~~~~~~~
repro.cpp:22:6: note: candidate: ‘template<class auto:1, class auto:2>  requires (Flequalable<auto:1>) && (Flequalable<auto:2>) bool flequal(const Bar<auto:1>&, const Bar<auto:2>&)’
   22 | bool flequal(Bar<Flequalable auto> const &a, Bar<Flequalable auto> const &b) {
      |      ^~~~~~~
repro.cpp:22:6: note:   template argument deduction/substitution failed:
repro.cpp:22:6: note: constraints not satisfied
repro.cpp: In substitution of ‘template<class auto:1, class auto:2>  requires (Flequalable<auto:1>) && (Flequalable<auto:2>) bool flequal(const Bar<auto:1>&, const Bar<auto:2>&) [with auto:1 = double; auto:2 = double]’:
repro.cpp:29:24:   required from here
repro.cpp:6:9:   required for the satisfaction of ‘Flequalable<auto:1>’ [with auto:1 = double]
repro.cpp:8:8:   in requirements with ‘T a’, ‘T b’ [with T = double]
repro.cpp:9:13: note: the required expression ‘flequal(a, b)’ is invalid, because
    9 |     {flequal(a, b)} -> std::convertible_to<bool>;
      |      ~~~~~~~^~~~~~
repro.cpp:9:13: error: ‘flequal’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
repro.cpp:22:6: note: ‘template<class auto:1, class auto:2>  requires (Flequalable<auto:1>) && (Flequalable<auto:2>) bool flequal(const Bar<auto:1>&, const Bar<auto:2>&)’ declared here, later in the translation unit
   22 | bool flequal(Bar<Flequalable auto> const &a, Bar<Flequalable auto> const &b) {
      |      ^~~~~~~

这里是准确的 GCC 版本信息:

Using built-in specs.
COLLECT_GCC=/usr/local/gcc-11-master/bin/g++-11
COLLECT_LTO_WRAPPER=/usr/local/gcc-11-master/libexec/gcc/x86_64-pc-linux-gnu/11.0.1/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ../gcc/configure --prefix=/usr/local/gcc-11-master --program-suffix=-11 --enable-libstdcxx-debug : (reconfigured) ../gcc/configure --prefix=/usr/local/gcc-11-master --program-suffix=-11 --enable-libstdcxx-debug
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 11.0.1 20210304 (experimental) (GCC) 

首先,Bar<Flequalable auto> 不是有效语法。您可以像 Concept auto v 这样声明一个顶级变量,但不能将其嵌套在模板中。所以它真的应该是:

template <Flequalable T, Flequalable U>
bool flequal(Bar<T> const &a, Bar<U> const &b) {
    return true;
}

现在,当我们尝试调用 flequal(a, b) 时会发生什么。我们推导出 T=doubleU=double,然后尝试评估 double 是否满足 Flequalabledoublefloating_point,所以我们继续检查 flequal(a, b) 是两个 double 的有效表达式的要求。

这是非限定查找,所以我们首先对名称flequal进行常规非限定查找。请务必记住,此查找是从 概念定义 的角度进行的,而不是从使用它的角度进行的。也就是说,我们从这里向上看:

#include <concepts>

using std::floating_point;

template< typename T >
concept Flequalable
    = floating_point<T>
    && requires(T a, T b) {
        {flequal(a, b)} -> std::convertible_to<bool>;  // <== here!
    };

不是使用概念的地方:

template <Flequalable T, Flequalable U> // <== not here
bool flequal(Bar<T> const &a, Bar<U> const &b) {
    return true;
}

也没有调用使用概念的函数模板的地方:

return flequal(a, b); // <== not here either

我们什么也没找到。从那时起没有可见的 flequal 声明。然后,我们进行参数相关查找。 double 没有 关联的命名空间,所以这个 also 也找不到任何东西。因此,候选者为零,因此 double 不满足 Flequalable.

为了完成这项工作,您必须确保概念 Flequalable 中的非限定查找实际上找到了您的 flequal 函数。只需交换两个声明,然后一切正常。


这使得 flequal 看起来像是某种自定义点,但只有固定数量的 floating_point 类型(floatdoublelong double,及其 cv 限定版本)所以我不完全确定这里的重点是什么。