C++ Logo

std-proposals

Advanced search

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

From: Jonathan Wakely <cxx_at_[hidden]>
Date: Sun, 10 Mar 2024 14:06:03 +0000
On Sun, 10 Mar 2024, 13:49 Frederick Virchanza Gotham via Std-Proposals, <
std-proposals_at_[hidden]> wrote:

> 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.
>

But this is exactly the same behaviour you get with Func(std::get<0>(t),
std::get<1>(t), std::get<2>(t)), or equivalently with std::move(t) for each
argument.

Why should apply work differently?

If you need this odd mix of value categories to work, it suggests your use
of tuples containing rvalue refs is wrong. The use case for doing that is
mostly things like forward_as_tuple where your tuple is also an rvalue.




> 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:
>


For the hundredth time, stop showing how to code something and demonstrate
why it's needed. Your motivation is "when I use tuple in a way that's not
expected to work, it doesn't work".

That's not motivation.




> 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_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2024-03-10 14:07:24