The following code is a simulation of an error I have in a larger code base. The idea is to collect various methods of calculating hashes of objects under one umbrella. The c++20 requires expression together with c++17 constexpr if provide a way to dispatch different strategies based on the input type.
It is entirely valid c++20 code to uses requires expression without concepts and outside a constraint on a template. A requires expression should just evaluate to a constexpr bool and is thus ok to use inside constexpr if.
The various operations are mocked out so as not to add unnecessary dependencies when demonstrating the issue.
https://godbolt.org/z/o79rTKfY8
#include <iostream>
struct A {
int a ;
std::string GetHashCode() const {
return "BHASH";
}
};
struct B {
int b ;
template <typename Archive>
void serialize(Archive & ar, int ) const
{
// No op
}
};
struct Archive {
std::string GetKey(){
return "GetArchiveKey";
}
};
template <typename T>
void operator & (Archive & ar, T & t){
t.serialize(ar, 0);
}
template <typename T>
std::string foo(T const & t)
{
if constexpr ( requires(Archive ar) { ar & t; }){
Archive ar;
ar & t;
return ar.GetKey();
}else if constexpr( requires { t.GetHashCode(); }){
return t.GetHashCode();
}else
return "NO HASH";
}
int main() {
std::cout << foo(A{}) << std::endl ;
std::cout << foo(B{}) << std::endl ;
}
The error is
<source>:27:7: error: no member named 'serialize' in 'A'
t.serialize(ar, 0);
~ ^
<source>:35:12: note: in instantiation of function template specialization 'operator&<const A>' requested here
ar & t;
^
<source>:45:18: note: in instantiation of function template specialization 'foo<A>' requested here
std::cout << foo(A{}) << std::endl ;
^
1 error generated.
This is strange because the error occurs in the following context.
if constexpr ( requires(Archive ar) { ar & t; }){
Archive ar;
ar & t;
return ar.GetKey();
}
where requires(Archive ar) {ar & t;} is passing but when the body of the if clause is compiled ar & t fails to compile. This is because deeper down the type requires a serialize method. I would have thought this would have been caught in the requires clause, removing this block from consideration.
ar & tis a valid expression. There is a function which exists and will be called in accord with the rules of C++ syntax.That's all
requirescares about. Checking whether something inside of that function is legitimate is notrequires's job.So if you want to test that, then you need to implement the
&operator in a way that it becomes invalid if something in its body would not work. Of course, since that's defined by some external code, you're more or less out of luck.