C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Defect report for std::apply (or apply_perfectly)

From: Tiago Freire <tmiguelf_at_[hidden]>
Date: Sun, 10 Mar 2024 14:38:28 +0000
It looks to me that the function did what you asked it to do. Doing it otherwise would be the unexpected behavior. What am I missing?
________________________________
From: Std-Proposals <std-proposals-bounces_at_[hidden]> on behalf of Frederick Virchanza Gotham via Std-Proposals <std-proposals_at_[hidden]>
Sent: Sunday, March 10, 2024 2:48:34 PM
To: std-proposals_at_[hidden] <std-proposals_at_[hidden]>
Cc: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Subject: Re: [std-proposals] Defect report for std::apply (or apply_perfectly)

On Sun, Mar 10, 2024 at 12:02 PM Jonathan Wakely wrote:
>
> Forward your tuple<T&&> as an rvalue not an lvalue.
>
> Why should std::apply work differently from everything else that uses std::get?


I'm not saying that std::get' should work differently here, but rather
that the result of it should be static_cast'ed before passing
arguments to the callable.

I can see one problem with passing the tuple object by Rvalue
reference, as demonstrated in the following code:

    #include <iostream> // cout, endl
    #include <string> // string
    #include <tuple> // get, tuple, apply
    #include <utility> // move

    using std::string;

    void Func(string &&a, string &b, string &&c)
    {
        static string s( std::move(a) );
    }

    int main(void)
    {
        string s("For oft when on my couch I lie, in vacant or in
pensive mood");

        std::tuple<string, string&, string&&> t = {
            "They flash upon that inward eye, which is the bliss of solitude",
            s,
            std::move(s)
        };

    // std::apply( Func, t ); - fails to compile

        std::apply( Func, std::move(t) );

        std::cout << "Daffodils: " << std::get<0>(t) << std::endl;
    }

The reason I pass the tuple by Rvalue reference to 'std::apply' is so
that the third parameter to Func, i.e. "string &&c", will bind
successfully. I also want the second parameter to bind successfully --
which is an Lvalue reference. I don't however want the first parameter
to bind successfully -- I don't want to lose the content of the first
string in my tuple -- I want a compiler error here, and so I really
shouldn't have passed my tuple by Rvalue reference to begin with.

And so therefore I think either:
(a) A defect report should be raised for 'apply' so that the return
value from 'get' should be static_cast'ed.
(b) A new function should be added to the standard library called
'apply_perfectly'

Here's how 'apply_perfectly' can be coded:

     https://godbolt.org/z/fqc7Wja9s

    I've left out the 'noexcept(noexcept(............))' so you can
read it a little more easily.

And here it is copy-pasted:

#include <functional> // invoke
#include <iostream> // cout, endl
#include <string> // string
#include <tuple> // get, tuple, tuple_element, apply
#include <type_traits> // conditional_t, is_lvalue_reference,
is_rvalue_reference, is_same, remove_reference
#include <utility> // forward, index_sequence, make_index_sequence, move

namespace std {
    template<typename F, typename Tuple, size_t... I>
    constexpr decltype(auto) apply_perfectly_impl(F &&f, Tuple &&t,
index_sequence<I...>)
    {
#define tElem tuple_element_t< I, remove_reference_t<Tuple> >
#define tElem_NoRef remove_reference_t< tElem >
#define vElem_IsRef (!is_same_v< tElem , tElem_NoRef >)
#define vElem_ShouldBeR (is_rvalue_reference_v< tElem > ||
(!vElem_IsRef && !is_lvalue_reference_v<Tuple>))
#define tElem_CastTo conditional_t< vElem_ShouldBeR ,
tElem_NoRef&&, tElem_NoRef& >

        return invoke( forward<F>(f), static_cast< tElem_CastTo >(
get<I>(t) )... );
    }

    template <typename F, typename Tuple>
    constexpr decltype(auto) apply_perfectly(F &&f, Tuple &&t)
    {
        return apply_perfectly_impl(
            forward< F>(f),
            forward<Tuple>(t),
            make_index_sequence< tuple_size_v< remove_cvref_t<Tuple> > >{});
    }
}

using std::string;

void Func1(string &a, string &b, string &&c)
{
    static string s( a );
}

void Func2(string &&a, string &b, string &&c)
{
    static string s( std::move(a) );
}

int main(void)
{
    string s("For oft when on my couch I lie, in vacant or in pensive mood");

    std::tuple<string, string&, string&&> t = {
        "They flash upon that inward eye, which is the bliss of solitude",
        s,
        std::move(s)
    };

    std::apply_perfectly( Func1, t );
    std::cout << "Daffodils: " << std::get<0>(t) << std::endl;

// std::apply_perfectly( Func1, std::move(t) ); // - fails to compile

// std::apply_perfectly( Func2, t ); // - fails to compile

    std::apply_perfectly( Func2, std::move(t) );
    std::cout << "Daffodils: " << std::get<0>(t) << std::endl;
}
--
Std-Proposals mailing list
Std-Proposals_at_lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2024-03-10 14:38:31