Search code examples
c++c++17std-variant

How can I pass the current alternative type of std::variant to a callable?


I'm invoking some user-defined callables with arguments determined at runtime. I have the following working:

using argument_t = std::variant< int, bool, double, std::string >;
using argument_list = std::vector< argument_t >;

template < typename Callable, std::size_t... S >
auto invoke_with_args_impl( Callable&& method, const argument_list& args, std::index_sequence< S... > )
{
    return std::invoke( method, args[S]... );
}

template < std::size_t size, typename Callable >
auto invoke_with_args_impl( Callable&& method, const argument_list& args )
{
    if ( args.size() != size )
    {
        throw std::runtime_error( "argument count mismatch" );
    }
    return invoke_with_args_impl( std::forward< Callable >( method ), args, std::make_index_sequence< size >() );
}

template < typename Callable >
auto invoke_with_args( Callable&& method, const argument_list& args )
{
    switch ( args.size() )
    {
        case 0:
            return method();
        case 1:
            return invoke_with_args_impl< 1 >( std::forward< Callable >( method ), args );
        //.... ad nauseam

This all works fine, however, argument_t must be the type of all the arguments in the user-defined function for it to work successfully.

I am trying to figure out how to pass the current alternative type of the variant instead.

Something like

return std::invoke( method, std::get< args[S].index() >( args[S] )... );

but obviously that doesn't work...unfortunately I'm at the end of my skills on this one.

How can this be done?


Solution

  • You might use something like:

    template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
    template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
    
    template < typename Callable, std::size_t... Is >
    auto invoke_with_args_impl(Callable&& method,
                               const argument_list& args,
                               std::index_sequence<Is...>)
    {
        return std::visit(overloaded{
            [&](auto&&... ts)
            -> decltype(std::invoke(method, static_cast<decltype(ts)>(ts)...))
            { return std::invoke(method, static_cast<decltype(ts)>(ts)...); },
            [](auto&&... ts)
            -> std::enable_if_t<!std::is_invocable<Callable, decltype(ts)...>::value>
            { throw std::runtime_error("Invalid arguments"); } // fallback
          },
          args[Is]...);
    }
    

    Demo