Search code examples
c++c++-concepts

How do I constrain the parameter(s) of a functor using concepts?


I have the following concept:

template <typename T>
concept FunctorInt = requires(T a, int b) {
    a.operator()(b); //require the () operator with a single int parameter.
};

I use this in the following function:

template <FunctorInt functor_t>
void for_each_sat_lit(const int ClauseIndex, functor_t functor) {
    auto LitIndex = -1;
    uint32_t Count = h_SatisfiedLitCount[ClauseIndex];
    if constexpr (!satlit) { Count = h_LiteralsInClauseCount[ClauseIndex] - Count; }
    for (uint32_t dummy = 0; dummy < Count; dummy++) {
        LitIndex = NextSetBit(h_SATLiteralBits[ClauseIndex], LitIndex);
        functor(LitIndex); //LitIndex must be an int.
    }
}

This compiles. However, when I try and break the code by changing the concept to

template <typename T>
concept FunctorInt = requires(T a, float b) {
    a.operator()(b); //I intend to require the () operator with a single float parameter.
};

It still compiles, meaning it did not constrain the functor at all.

How do I constrain a functor so that it can only have a single int parameter?

MSVC: concepts.cpp

#include <concepts>

template <typename T>
concept FunctorInt = requires(T a, int b) {
    a.operator()(b); //require the () operator with a single int parameter.
};

template <typename T>
concept FunctorFloat = requires(T a, float b) {
    a.operator()(b); //require the () operator with a single float parameter.
};


void Loop(FunctorInt auto functor) {
    for (auto i = 0; i < 10; i++) {
        functor(i);
    }
}

void LoopShouldNotCompile(FunctorFloat auto functor) {
    for (auto i = 0; i < 10; i++) {
        functor(i); //<< i is not a float.
    }
}

int main(const int argc, const char* argv[]) {
    Loop(                [](int   a){ printf("%i", a); });
    LoopShouldNotCompile([](float a){ printf("%f", a); });
}

If I change the definitions of FunctorInt and FunctorFloat using std::invocable, the same problem persists:

concept FunctorInt = std::invocable<int>;

concept FunctorFloat = std::invocable<float>;

Everything still compiles, whereas it should give a compile error on LoopShouldNotCompile.

UPDATE:

I settled on:

template <typename T>
concept FunctorInt = 
    requires() { [](void(T::*)(int) const){}(&T::operator()); }
 || requires() { [](void(T::*)(int)      ){}(&T::operator()); };

Which creates a functor that only allows a single int parameter and doesn't care about const-correctness.


Solution

  • Your concept check if the type can be called with an int but you cannot control promotion/conversion which happens before.

    You can check signature of T::operator(), but then previous valid cases (as overload, template function, no exact function but similar (const, volatile, ...)) might no longer work.

    For example:

    template <typename T>
    concept FunctorInt = requires() {
        [](void(T::*)(int) const){}(&T::operator());
    };
    
    void Loop(FunctorInt auto functor) { /**/ }
    
    int main() {
        Loop([](int   a){ printf("%i", a); });
        Loop([](float a){ printf("%f", a); }); // Not compile
    }
    

    Demo