I have a concept that helps me detect the signature of a function:
template <typename>
struct FuncHelper;
template <typename R, typename... Args>
struct FuncHelper<R(Args...)> {
template <typename Func>
static constexpr bool is_like = requires(const Func& f, Args... args) {
{ f(std::forward<Args>(args)...) } -> std::convertible_to<R>;
};
};
template <typename FX, typename T>
concept function_like = FuncHelper<FX>::template is_like<T>;
I can use this concept in function overloads as a constraint:
template <typename T> requires function_like<bool(), T>
void testWorks(T&& func) { }
// I can call this like testWorks([&](){ return true; });
However if I specify this constraint within the template parameters:
template <function_like<bool()> T>
void testFails(T&& func) { }
// testFails([&](){ return true; }); // Compiler error: incomplete type
then I get a compiler error saying that I have an incomplete type.
Here is the full code:
#include <concepts>
#include <type_traits>
#include <utility>
using namespace std;
template <typename>
struct FuncHelper;
template <typename R, typename... Args>
struct FuncHelper<R(Args...)> {
template <typename Func>
static constexpr bool is_like = requires(const Func& f, Args... args) {
{ f(std::forward<Args>(args)...) } -> std::convertible_to<R>;
};
};
template <typename FX, typename T>
concept function_like = FuncHelper<FX>::template is_like<T>;
template <typename T> requires function_like<bool(), T>
void testWorks(T&& func) { }
template <function_like<bool()> T>
void testFails(T&& func) { }
void testAll() {
testWorks([&](){ return true; }); // No errors!
testFails([&](){ return true; }); // Compiler error: incomplete type
}
You can try it out yourself:
GCC: https://godbolt.org/z/fG6c7E3qf
Clang: https://godbolt.org/z/7G7T8nTde
I thought that both testWorks
and testFails
were supposed to do the same thing. Where am I going wrong?
CodePudding user response:
These two:
template <typename T> requires function_like<bool(), T>
void testWorks(T&& func) { }
template <function_like<bool()> T>
void testFails(T&& func) { }
are not equivalent. The latter is equivalent to:
template <typename T> requires function_like<T, bool()>
void testFails(T&& func) { }
Note the different order of parameters into function_like
.
The issue is that your concept isn't built to handle the type-constraint syntax properly. You need to flip your parameters:
template <typename T, typename Sig>
concept function_like = FuncHelper<Sig>::template is_like<T>;
and then both of these work fine (because now they actually are equivalent):
template <typename T> requires function_like<T, bool()>
void testWorks(T&& func) { }
template <function_like<bool()> T>
void testFails(T&& func) { }