Reputation: 1542
I'm experimenting with C++20 concepts, using my own local build of a clone of the GCC 11 source code. I'm getting compilation errors from GCC that seem wrong to me. I've reduced my code triggering the diagnostic to what's below, with the GCC command line to compile it and the full resulting diagnostic output below that.
For focus, the particular section of the diagnostic output that seems wrong to me is
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) {
| ^~~~~~~
It says the candidate function was declared later in the translation unit than the point of instantiation, but that is not true, at least going by source line order in my code snippet.
Is this build error wrong, or am I doing something wrong in my use of C++20 concepts?
Note the variant, in a comment at the end of the code, which compiles without any diagnostic.
Code:
#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
}
Compilation command-line:
/usr/local/gcc-11-master/bin/g++-11 -c repro.cpp -o repro.o -std=c++20 -fconcepts-diagnostics-depth=5 2> diagnostic.txt
Full GCC diagnostic:
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) {
| ^~~~~~~
Here's the exact GCC version information:
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)
Upvotes: 1
Views: 1446
Reputation: 302862
First, Bar<Flequalable auto>
is not valid syntax. You can have a top-level variable declared like Concept auto v
but you can't nest that within templates. So it really should be:
template <Flequalable T, Flequalable U>
bool flequal(Bar<T> const &a, Bar<U> const &b) {
return true;
}
Now, what happens when we try to call flequal(a, b)
. We deduce T=double
and U=double
and then try to evaluate if double
satisfies Flequalable
. double
is floating_point
, so we keep going to check the requirement that flequal(a, b)
is a valid expression for two double
s.
This is unqualified lookup, so we first do regular unqualified lookup for the name flequal
. It's important to keep in mind that this lookup happens from the point of the concept definition, not from the point where it is used. That is, we're looking up from here:
#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!
};
Not where the concept is used:
template <Flequalable T, Flequalable U> // <== not here
bool flequal(Bar<T> const &a, Bar<U> const &b) {
return true;
}
Nor where the function template using the concept is invoked:
return flequal(a, b); // <== not here either
We find nothing. There are no declarations of flequal
visible from that point. Then, we do argument-dependent lookup. double
has no associated namespaces, so this also trivially finds nothing. As such, there are zero candidates, so double
does not satisfy Flequalable
.
In order to make this work, you have to make sure that unqualified lookup in the concept Flequalable
actually finds your flequal
function. Just swap the two declarations, and then everything works fine.
This makes it seem like flequal
is some kind of customization point, but there are only a fixed number of floating_point
types (float
, double
, long double
, and cv-qualified versions thereof) so I'm not entirely sure what the point here is.
Upvotes: 4