Date: Sun, 10 Mar 2024 13:48:34 +0000
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;
}
>
> 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;
}
Received on 2024-03-10 13:48:43