Is it possible to have a function like std::invoke
, but this function calls all arguments of the given function automatically with the default constructed types?
#include <iostream>
#include <functional>
// e.g. for a single arg
struct Test{
void operator()(int i) {
std::cout << std::to_string(i) << "\n";
}
};
int main(){
Test test;
std::invoke(test, {}); // this doesn't work, would like it to call with default constructed int (0).
return 0;
}
I would like something like
int main()
{
Test test;
invoke_with_defaults(test); // prints 0
return 0;
}
CodePudding user response:
You need a class with a templated conversion operator
, returning {}
for any type:
struct DefaultConstruct
{
DefaultConstruct() = default;
DefaultConstruct(const DefaultConstruct &) = delete;
DefaultConstruct &operator=(const DefaultConstruct &) = delete;
template <typename T> operator T() && {return {};}
};
int main()
{
Test test;
std::invoke(test, DefaultConstruct{});
}
It's then possible to write a template that automatically determines how many of those have to be passed:
template <typename F, typename ...P>
decltype(auto) InvokeDefault(F &&func)
{
if constexpr (std::is_invocable_v<F, P...>)
return std::invoke(std::forward<F>(func), P{}...);
else
return InvokeDefault<F, P..., DefaultConstruct>(std::forward<F>(func));
}
int main()
{
Test test;
InvokeDefault(test);
}
And if the argument isn't callable at all, you get a compilation error after exceeding some implementation-defined limit (on Clang I got up to 256).
CodePudding user response:
Initializer lists like {}
cannot be forwarded as a parameter due not work due to language restrictions.
But you can mimick {}
by wrapping it into a Defaulter
class which can be passed around:
#include <iostream>
#include <functional>
// e.g. for a single arg
struct Test{
void operator()(int i) {
std::cout << std::to_string(i) << "\n";
}
};
struct Defaulter{
template<typename T>
operator T(){
return {};
}
};
int main(){
Test test;
std::invoke(test, Defaulter{});
return 0;
}
CodePudding user response:
You could use something like this to create a tuple of all of the argument types, and then pass a default constructed instance of it to std::apply
. The specialisation list would need to be quite long though to cover all of the const
, volatile
, noexcept
, and ref-qualified variants though, and of course it cannot work with template or overloaded functions.
Eg:
template <typename T>
struct arg_extractor : arg_extractor<decltype(&T::operator())> {
};
template <typename R, typename... Args>
struct arg_extractor<R (*)(Args...)> {
using type = std::tuple<R, Args...>;
};
template <typename R, typename C, typename... Args>
struct arg_extractor<R (C::*)(Args...)> {
using type = std::tuple<R, Args...>;
};
template <typename R, typename C, typename... Args>
struct arg_extractor<R (C::*)(Args...) const> {
using type = std::tuple<R, Args...>;
};
template <typename R, typename C, typename... Args>
struct arg_extractor<R (C::*)(Args...) noexcept> {
using type = std::tuple<R, Args...>;
};
template <typename R, typename C, typename... Args>
struct arg_extractor<R (C::*)(Args...) const noexcept> {
using type = std::tuple<R, Args...>;
};
// All the rest...
template <typename T>
using arg_extractor_t = typename arg_extractor<T>::type;