c++c++20c++-conceptsrequires-clause

Why does the cpp requires expression sometimes allow non valid expressions?


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.


Solution

  • 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.