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