Date: Fri, 14 Jun 2024 12:14:07 +0100
On 14/06/2024 11:04, Frederick Virchanza Gotham via Std-Proposals wrote:
> 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.
You don't need to use either std::tuple or std::function. You can avoid
type-erasing the lambda that you are capturing in the std::function and
store it directly.
Cheers,
Lénárd
> 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.
You don't need to use either std::tuple or std::function. You can avoid
type-erasing the lambda that you are capturing in the std::function and
store it directly.
Cheers,
Lénárd
Received on 2024-06-14 11:14:09