I'm storing an std::function
inside a variadic class template (passed in the constructor of the class).
While doing so, I want to check that the types of the std::function
parameters are the same as the types within the parameter pack of the class template. Here is an example:
template<typename... T>
class Foo {
public:
explicit Foo(std::function<void(T...)> f)
: func(std::move(f)) {
// static_assert(...) How to formulate the static_assert here?
}
std::function<void(T...)> func;
};
int main()
{
Foo<int> fooA([](int& a){}); // does not compile
Foo<int&> fooB([](int a){}); // should not compile, but does
Foo<int&> fooC([](int& a){}); // should compile
}
If a class Foo<int&>
is defined with a reference type, I'd like to make sure that the lambda also takes an int
by reference and not by value.
The other way around (Foo<int>
and lambda with an int&
) already does not compile as that lambda cannot be converted to a function taking an int
by value.
Using a static_assert()
, I tried to ensure that the type of the parameter in the lambda matches the type given as a template parameter to Foo
.
So far, I have tried unwrapping the function arguments:
template<typename... T>
struct ID {};
template<typename Func>
struct Unwrap;
template<typename R, typename... Args>
struct Unwrap<R(Args...)> {
using ArgsType = ID<Args...>;
};
template<typename R, typename... Args>
struct Unwrap<std::function<R(Args...)>>
: Unwrap<R(Args...)> {};
...
static_assert(std::is_same<typename Unwrap<std::function<void(T...)>>::ArgsType, ID<T...>>::value, "");
...
Is there a way to achieve what I want? (Does it even make sense for me to think about such a check on my end, or should someone using my class Foo
make sure, that he provides a lambda with the correct parameter types?)
If you can use C++17... what about using the CTAD defined for std::function
to check that the std::function
deduced for the argument of the constructor is exactly the same of func
?
I mean... what about as follows?
template <typename... T>
struct Foo
{
template <typename L>
Foo (L && l) : func{ std::forward<L>(l) }
{
using T1 = decltype(func);
using T2 = decltype(std::function{std::forward<L>(l)});
static_assert( std::is_same_v<T1, T2>, "a better failure message, please" );
}
std::function<void(T...)> func;
};
Using this Foo
you get
//Foo<int> fooA([](int& a){}); // compilation error (assigning func)
//Foo<int&> fooB([](int a){}); // compilation error (static_assert failure)
Foo<int&> fooC([](int& a){}); // compile
--- EDIT ---
The OP observe that
Foo<void(int&)> fooD([](auto&) {});
would not compile
Unfortunately, this solution impose a std::function
parameters CTAD deduction, so gives a compilation error when the argument is a generic lambda.
We can SFINAE deactivate the static_assert()
in case the std::function
is not deducible
constexpr std::false_type Bar (...);
template <typename L>
constexpr auto Bar (L && l)
-> decltype( std::function{std::forward<L>(l)}, std::true_type{} );
template <typename... T>
struct Foo
{
template <typename L>
Foo (L && l) : func{ std::forward<L>(l) }
{
if constexpr ( decltype(Bar(std::forward<L>(l)))::value == true )
{
using T1 = decltype(func);
using T2 = decltype(std::function{std::forward<L>(l)});
static_assert( std::is_same_v<T1, T2>, "a better message, please" );
}
}
std::function<void(T...)> func;
};
So
Foo<int &> fooF([](auto &){});
become compilable.
Continue to gives an error a generic lambda receiving a generic reference when a value is requested
//Foo<int> fooD([](auto &){}); // compilation error (assigning func)
because remain the error assigning func
.
The problem is that, deactivating the test, the following code compile
Foo<int &> fooE([](auto){}); // compile (!)
and I don't know how to avoid it.