Search code examples
c++templatesc++20c++-concepts

Can a C++ concept check that a type is compatible with any type of another concept?


I want to create a concept Handler that will check that implementers are able to accept any types that satisfy the concept Update - without actually having to instantiate the concept with every single possible update object that I have. In my head, it would look something like this:

template <typename T>
concept Update = requires (T update) {
  { update.get_value(); } -> std::same_as<std::string>;
  ...
};

template <typename T>
concept Handler = requires (T handler, Update update) {
  handler.on_update(update);
};

However this doesn't actually work unless I add an additional parameter to the Handler concept and pass a specific instance of the Update concept. Is there any way to achieve what I'm trying to do? I have considered creating a mock FakeUpdate object that implements the Update concept (which is really what I'm hoping the compiler will do), but that feels a little ugly.


Solution

  • The following example shows a bit of a hacky way to achieve what you want. We simply define the UpdateableDummy struct, and pass it to our Handleable concept. It would be nice if the Handleable concept could deduce the type passed to on_update from the template of the Handleable class, but this would fail for classes that are indeed Handleable but not templates (see non_templateHandleableExample). So perhaps in a future language standard, the type of the call to on_update in the Handleable concept, could be deduced from the class being checked for Handleablelity

    It should also be noted, that template <Updateable T> in the declaration of HandleableExample requires that T be Updateable.

    // Example program
    #include <iostream>
    #include <string>
    #include <concepts>
    
    template <typename T>
    concept Updateable = requires (T update) {
      { update.get_value() } -> std::same_as<std::string>;
    };
    
    // Updateable Dummy Type
    struct UpdateableDummy {
        std::string get_value(void) {
            return "dummyValue";
        }
    };
    
    template <typename U, typename V>
    concept Handleable = requires (U handler, V updateableInstance) {
        handler.on_update(updateableInstance);
    };
    
    // Updateable Example Type
    struct UpdateableExample {
        std::string get_value(void) {
            return "exampleValue";
        }
    };
    
    // Not Updateable Example Type
    struct NotUpdateableExample {
        void print_value(void) {
            std::cout << "dummyValue\n";
        }
    };
    
    // Handleable Example Type
    template <Updateable T>
    struct HandleableExample {
        void on_update(T t) {
            t.get_value();
        }
    };
    
    // Not Handleable Example Type
    template <Updateable T>
    struct NotHandleableExample {
        void when_update(T t) {
            t.get_value();
        }
    };
    
    // Handleable Struct that doesn't use templates
    struct non_templateHandleableExample {
        void on_update(UpdateableExample updateableInstance) {
            updateableInstance.get_value();
        }
    };
    
    // Works With Both UpdateableDummy and UpdateableExample Type
    static_assert(Handleable<HandleableExample<UpdateableDummy>,UpdateableDummy>);
    static_assert(Handleable<HandleableExample<UpdateableExample>,UpdateableExample>);
    
    // Works with non_templateHandleableExample
    static_assert(Handleable<non_templateHandleableExample,UpdateableExample>);
    
    // Fails For not updateable inner-type
    static_assert(Handleable<HandleableExample<NotUpdateableExample>,NotUpdateableExample>);
    
    // Fails For not handleable outer-type
    static_assert(Handleable<NotHandleableExample<UpdateableDummy>,UpdateableDummy>);
    
    // Fails For not handleable outer-type AND not updateable inner-type
    static_assert(Handleable<NotHandleableExample<NotUpdateableExample>,NotUpdateableExample>);
    
    
    int main()
    {
      // Comment out intentionally failing static assertions to test the auto type keyword example
      Handleable<UpdateableExample> auto handleableInstance = HandleableExample<UpdateableExample>();
    }