Home > Blockchain >  Is it possible to call a function with all arguments default constructed?
Is it possible to call a function with all arguments default constructed?

Time:12-29

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;
  •  Tags:  
  • c
  • Related