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 & t
is a valid expression. There is a function which exists and will be called in accord with the rules of C++ syntax.
That's all requires
cares about. Checking whether something inside of that function is legitimate is not requires
'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.