C++ Logo

std-proposals

Advanced search

[std-proposals] std::constant_size_function

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Fri, 14 Jun 2024 11:04:12 +0100
In my paper on "std::elide", I wrote a possible implementation of the
class. I have minimised the implementation of "std::elide" here (e.g.
removed all the 'noexcept' stuff) just to use it as an example. Note
my use of a tuple of references in the following class:

    template<typename F_ref, typename... Params_refs>
    class elide {
        typedef invoke_result_t< F_ref, Params_refs... > R;
        typedef remove_reference_t<F_ref> F;
        F &&f; // 'f' is always an Rvalue reference
        tuple< Params_refs... > const args_tuple; // just a tuple
full of references
    public:

        template<typename F, typename... Params>
        elide(F &&arg, Params&&... args) // see explicit deduction guide below
        : f(move(arg)), args_tuple( static_cast<Params_refs>(args)... ) {}

        operator R(void)
        {
            return apply( static_cast<F_ref>(f), std::move(args_tuple) );
        }
    };

    template<typename F, typename... Params>
    elide(F&&,Params&&...) -> elide<F&&,Params&&...>; // explicit
deduction guide

Note my use of a 'tuple' in the above code. I could make the class
much more simple by using 'std::function' as follows:

    template<typename F_ref, typename... Params_refs>
    class elide {
        typedef invoke_result_t< F_ref, Params_refs... > R;
        std::function<R(void)> stdfunc;
    public:

        template<typename F, typename... Params>
        elide(F &&arg, Params&&... args)
        : stdfunc( [&arg,&args...](void)
                    {
                        return forward<F>(arg)( forward<Params>(args)... );
                    } ) {}

        operator R(void)
        {
            return stdfunc();
        }
    };

There's only one problem here. The size of 'std::function<R(void)>' is
a compile-time constant. I think the libstdc++ implementation has 16
or 32 bytes of free space inside it for the captures -- and if the
captures are bigger than that then you get dynamic memory allocation.

But what if we had a new template class called
'std::constant_size_function' which we would use as follows. Note that
there is a new template parameter 'size_of_lambda', and look at how
I've changed the deduction guide:

    template<size_t size_of_lambda, typename F_ref, typename... Params_refs>
    class elide {
        typedef invoke_result_t< F_ref, Params_refs... > R;
        std::constant_size_function< size_of_lambda, R(void) > func;
    public:

        template<typename F, typename... Params>
        elide(F &&arg, Params&&... args) // see explicit deduction guide below
        : func( [&arg,&args...](void)
                {
                    return forward<F>(arg)( forward<Params>(args)... );
                } ) {}

        operator R(void)
        {
            return func();
        }
    };

    template<typename F, typename... Params>
    elide(F &&arg,Params&&... args) -> elide<
sizeof([&arg,&args...](){}), F&&, Params&&... >; // explicit
deduction

See how I use the deduction guide to get the size of the lambda.
Something along those lines anyway. No dynamic allocation needed even
if you have 672 captures totalling 12 kilobytes.

Received on 2024-06-14 10:04:24