The following code intends to provide a concept for functions with a given signature and arity:
#include<utility> //std::index_sequence
#include<cstddef> //std::size_t
#include<functional> //std::invoke
struct A {};
struct B {};
auto f1 = [](auto) -> A { return {}; }; //should work also for generic arguments
auto f3 = [](A,A,A) -> A { return {}; };
auto g1 = [](B) -> B { return {}; };
//a unary A-function is a function A -> A
template<typename function_t>
concept is_unary_A_function = requires
{
{ std::invoke(std::declval<function_t>(), std::declval<A>()) } -> std::same_as<A>;
};
//an Nary A-function is a function with signature A x A x ... A (N-times) -> A
template<typename function_t, std::size_t N>
concept is_Nary_A_function = requires
{
{ []<size_t ... I>(auto function, auto a, std::index_sequence<I ...>)
{
return std::invoke(function, (I, a) ...);
}(std::declval<function_t>(), std::declval<A>(), std::make_index_sequence<N>()) } -> std::same_as<A>;
};
static_assert(is_unary_A_function<decltype(f1)>); //OK
static_assert(!is_unary_A_function<decltype(g1)>); //OK
static_assert(!is_unary_A_function<decltype(f3)>); //OK
static_assert(is_Nary_A_function<decltype(f3), 3>); //OK
//static_assert(!is_Nary_A_function<decltype(f1), 3>); //compile-time error
In words, the is_unary_A_function
concept should be true if it is inserted a function f1: A -> A
, i.e. a function which takes a parameter of type A and returns a type A. The is_Nary_A_function
with a given arity N
concept should be true for a function fN : A x A x ... A -> A
, that is a function which takes N parameters of type A and again returns an A
.
Here, in the is_Nary_A_function
concept, the usual approach using index_sequences is applied to call the provided function with N parameters of type A
.
However, as soon as I evaluate the the concept is_Nary_A_function<decltype(f1), 3>
, I get a compile-time error stating no matching function for call to 'invoke' which basically complains that the compiler can't call f1(a,a,a)
. However, the compiler also can't call g1(a)
, which is checked in the !is_unary_A_function<decltype(g1)>
expression, but this seems to work correctly.
Thus, the lambda closure in the N-ary function concept seems to make a difference. Can someone please explain a reason for that and show a possible workaround?
By making your lambda SFINAE friendly, your code works:
//an Nary A-function is a function with signature A x A x ... A (N-times) -> A
template<typename function_t, std::size_t N>
concept is_Nary_A_function = requires
{
{ []<size_t ... I>(auto function, auto a, std::index_sequence<I ...>)
-> decltype(std::invoke(function, (I, a) ...))
{
return std::invoke(function, (I, a) ...);
}(std::declval<function_t>(), std::declval<A>(), std::make_index_sequence<N>()) } -> std::same_as<A>;
};
Demo.
Note: requires
has syntax to allow to avoid std::declval
:
//a unary A-function is a function A -> A
template<typename function_t>
concept is_unary_A_function = requires
{
{ std::invoke(std::declval<function_t>(), std::declval<A>()) } -> std::same_as<A>;
};
can simply be
//a unary A-function is a function A -> A
template<typename function_t>
concept is_unary_A_function = requires(function_t f, A a)
{
{ std::invoke(f, a) } -> std::same_as<A>;
};