Date: Sun, 10 Mar 2024 14:28:38 +0000
On Sun, Mar 10, 2024 at 11:55 AM Jens Maurer wrote:
>
> Maybe [temp.deduct.general] p8 or so would be a more appropriate place
> to talk about SFINAE stuff.
Yeah, if I want the substitution to fail without a compiler error --
and without the code becoming ill-formed -- then I should look a
slipping a paragraph somewhere into that section.
> Why do you need an extra function instead of using class template
> argument deduction for std::elide_t directly?
I spent a few hours trying to combine the class and the function into
one. It's possible to do so if the callable is a simple function
pointer. However if the callable has multiple function-call-operators
then you need both a function and a class, i.e. 'elide' and 'elide_t'.
Consider the following class I used to torture-test it:
class Tester {
public:
counting_semaphore<5> operator()(void) &
{
return counting_semaphore<5>(1);
}
counting_semaphore<8> operator()(void) const &
{
return counting_semaphore<8>(1);
}
counting_semaphore<7> operator()(void) const &&
{
return counting_semaphore<7>(1);
}
counting_semaphore<6> operator()(void) &&
{
return counting_semaphore<6>(1);
}
};
If you are able to write just one class to accommodate such a bizarre
test class, without needing to have a separate standalone function,
then I'll be very intrigued to see how you pulled it off. I spent a
few hours at it, even trying stuff like:
operator decltype(auto)()
{
return std::apply(f, args);
}
I wasn't able to retain the Lvalue-or-Rvalue-ness of the function
parameters without separating it into a class and a standalone
function.
> So, without the core language change, if AwkwardClass ever wants to
> participate in no-copy creation, it would need to add a requires-clause.
Yes, the template constructor would need the following constraint:
requires (!is_specialization_v<remove_cvref_t<T>, elide_t>)
> Seems a small price to pay.
But the whole point of all of this is that we don't have to edit the
original classes such as std::optional, std::variant, boost::whatever.
If the class has a canonical 'emplace' method, then this proposal can
enable us to elide the copy/move operation without the need to edit
the original class definitions.
If we can edit the original class definitions then we may as well just
add a method called 'emplace_with_return_value_from_invocation_of'.
> I'd like to point out that we already have std::piecewise_construct for
> a similar purpose.
Interesting, I hadn't seen this before. A good example of where the
distinction between language and library has become mushed.
> For the avoidance of doubt, I'm opposed to the core language change.
Until you're dealing with allocators. The below code will invoke the
constructor of 'MonkeyAllocator' with T set to a specialisation of
'std::elide_t' -- which of course we don't want.
class Monkey {};
struct MonkeyAllocator : std::allocator<Monkey> {
std::mutex m; // cannot move, cannot copy
template<typename T>
MonkeyAllocator(T &&arg) : std::allocator<Monkey>( std::forward<T>(arg) )
{
cout << "MonkeyAllocator constructed with T = " <<
typeid(T).name() << endl;
}
};
MonkeyAllocator GiveMeMonkeyAllocator(void)
{
return MonkeyAllocator( std::allocator<int>() );
}
int main(void)
{
std::optional<MonkeyAllocator> var;
var.emplace( std::elide(GiveMeMonkeyAllocator) );
}
>
> Maybe [temp.deduct.general] p8 or so would be a more appropriate place
> to talk about SFINAE stuff.
Yeah, if I want the substitution to fail without a compiler error --
and without the code becoming ill-formed -- then I should look a
slipping a paragraph somewhere into that section.
> Why do you need an extra function instead of using class template
> argument deduction for std::elide_t directly?
I spent a few hours trying to combine the class and the function into
one. It's possible to do so if the callable is a simple function
pointer. However if the callable has multiple function-call-operators
then you need both a function and a class, i.e. 'elide' and 'elide_t'.
Consider the following class I used to torture-test it:
class Tester {
public:
counting_semaphore<5> operator()(void) &
{
return counting_semaphore<5>(1);
}
counting_semaphore<8> operator()(void) const &
{
return counting_semaphore<8>(1);
}
counting_semaphore<7> operator()(void) const &&
{
return counting_semaphore<7>(1);
}
counting_semaphore<6> operator()(void) &&
{
return counting_semaphore<6>(1);
}
};
If you are able to write just one class to accommodate such a bizarre
test class, without needing to have a separate standalone function,
then I'll be very intrigued to see how you pulled it off. I spent a
few hours at it, even trying stuff like:
operator decltype(auto)()
{
return std::apply(f, args);
}
I wasn't able to retain the Lvalue-or-Rvalue-ness of the function
parameters without separating it into a class and a standalone
function.
> So, without the core language change, if AwkwardClass ever wants to
> participate in no-copy creation, it would need to add a requires-clause.
Yes, the template constructor would need the following constraint:
requires (!is_specialization_v<remove_cvref_t<T>, elide_t>)
> Seems a small price to pay.
But the whole point of all of this is that we don't have to edit the
original classes such as std::optional, std::variant, boost::whatever.
If the class has a canonical 'emplace' method, then this proposal can
enable us to elide the copy/move operation without the need to edit
the original class definitions.
If we can edit the original class definitions then we may as well just
add a method called 'emplace_with_return_value_from_invocation_of'.
> I'd like to point out that we already have std::piecewise_construct for
> a similar purpose.
Interesting, I hadn't seen this before. A good example of where the
distinction between language and library has become mushed.
> For the avoidance of doubt, I'm opposed to the core language change.
Until you're dealing with allocators. The below code will invoke the
constructor of 'MonkeyAllocator' with T set to a specialisation of
'std::elide_t' -- which of course we don't want.
class Monkey {};
struct MonkeyAllocator : std::allocator<Monkey> {
std::mutex m; // cannot move, cannot copy
template<typename T>
MonkeyAllocator(T &&arg) : std::allocator<Monkey>( std::forward<T>(arg) )
{
cout << "MonkeyAllocator constructed with T = " <<
typeid(T).name() << endl;
}
};
MonkeyAllocator GiveMeMonkeyAllocator(void)
{
return MonkeyAllocator( std::allocator<int>() );
}
int main(void)
{
std::optional<MonkeyAllocator> var;
var.emplace( std::elide(GiveMeMonkeyAllocator) );
}
Received on 2024-03-10 14:28:48