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

Time:02-04

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.

CodePudding user response:

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

CodePudding user response:

If you want to block a function from accepting any other type that you want, you can use a type matching function template... e.g.

#include <concepts>

template <typename T>
requires std::same_as<T,float>
void func([[maybe_unused]]T f) {}

int main() {
    //func(1); // doesn't compile
    func(1.0f); // works
    //func(1.0); // NB: fails again, because float!=double
}

edit: shorter

void func([[maybe_unused]]std::same_as<float> auto f) {}
  •  Tags:  
  • Related