Why does my concept not work if specifying two requirements

Viewed 71

I have the following code:

#include <concepts>
#include <functional>
#include <iostream>

template<typename T>
concept OperatorLike = requires(T t, const std::string s) {
    { t.get_string(s) } -> std::same_as<const std::string>;
    { t.get_int(s) } -> std::same_as<const int>;

};

template<typename T, typename O>
concept Gettable = requires(T t, O op) { 
    t.apply_get(0, op);  
t.apply_post(0, op); };

template<std::semiregular F>
class RestApiImpl {
    F m_get_method;
    public:
    RestApiImpl(F get = F{}) : m_get_method{std::move(get)} {}
    void register_get(F functor) {
       m_get_method = std::move(functor);
    }
    template<OperatorLike IF>
        requires std::invocable<F, const int, IF>
    void apply_get(const int req, IF interface){ 
        m_get_method(req, std::move(interface));  
    }

        template<OperatorLike IF>
        requires std::invocable<F, const int, IF>
    void apply_post(const int req, IF interface){ 
        m_get_method(req, std::move(interface));  
    }
};

class ASpecificJSONLibrary{
    
    public:
    std::string operator[](std::string key){
        return key + "_withLambda";
    }
};

class Server{
public:
   ASpecificJSONLibrary libObj; // this is a 

   struct impl;

   void run(Gettable<impl> auto& api){
      api.apply_get(0, impl(*this));
   }

   struct impl {
        public:
        Server& m_server;
        impl(Server& server ) : m_server(server){}
        const std::string get_string(const std::string key) {
            return (m_server.libObj)[key];
        };
        const int get_int(std::string const key) {
            return 1;
        }
   };
};

int main(){
    auto get = [](int,  OperatorLike auto intf){ 
            std::string dummy = "dummy";
        std::cout << intf.get_string(dummy);
    };
    RestApiImpl api(get);
    
    Server server;
    server.run(api);
    
    return 0;
};

If remove the line t.get_int(s) } -> std::same_as<int>; from the concept OperatorLike everything seems to work as it should. I am not sure why adding this additional requirement gives me a compile error. The compile error also refers only to the fact that the there is no matching function to the run method, which is because the Gettable concept is not satisfied. But the root cause looks like it is the OperatorLike concept

Another observation just made is that if I remove all the consts from the operatorLike concept it all just seem to work including the additional requirements

1 Answers

The compound requirement

{ t.get_int(s) } -> std::same_as<const int>;

tests whether decltype((t.get_int(s))) is the same as const int. However, that is a pointless test, because there are no const prvalues of non-class type and these would be the only expressions for which decltype could result in const int.

A prvalue of non-class type will always have its const stripped. So even if get_int is declared to return const int, the type of the call expression t.get_int(s) will always be int, not const int.

In general having top-level const on a return type is almost always useless. The only use case I can think of is a class type return value for which you want to disallow calling non-const member functions without storing in a variable first, but there are better ways of achieving that (e.g. &-qualified member functions).

Related