Topic: Library facility to "merge" functions objects into one?
Author: Nicolas Lesser <blitzrakete@gmail.com>
Date: Thu, 2 Nov 2017 11:30:47 -0700 (PDT)
Raw View
------=_Part_2181_1466724957.1509647447121
Content-Type: multipart/alternative;
boundary="----=_Part_2182_1715082683.1509647447121"
------=_Part_2182_1715082683.1509647447121
Content-Type: text/plain; charset="UTF-8"
I don't know if this is a good idea or not, and that's why I need other
opinions.
It looks like it'd be nice if we could have a simple non-verbose way to
make a function object with multiple overloaded call operators. Currently,
this is only possible by either manually creating the function object (but
this has the same drawbacks as function objects with only one call operator
before lambdas were introduced) or by simulating overload resolution:
auto first = [](int) { return 1; };
auto second = [](double) { return 1.5; };
auto generic = [&first, &second]<typename... Ts>(Ts&&... args) {
// no references (or cv qualifiers) please
// this gets huge with function objects with different argument
amount... :(
if constexpr(sizeof...(args) == 1) {
if constexpr(std::is_convertible_v<std::decay_t<Ts>..., int>)
first(std::forward<Ts>(args)...);
else if constexpr(std::is_convertible_v<std::decay_t<Ts>...,
double>)
second(std::forward<Ts>(args)...);
else
// static_assert(false) but with dependent condition...
}
else
// static_assert(false) but with dependent condition...
}
But even this solution isn't perfect: The call wouldn't be ambiguous for a
type that is convertible to both int and double, and to support that we
need way more if conditions. And that's not good (and unmaintainable).
But unlike lambdas, I don't think that we should introduce yet another
syntax for lambdas with multiple call operators. Rather, I think it would
be better if we introduce some sort of library class that would take care
of merging function objects' call operators into a function object which
has every one of them. Here's how it could look like:
template<typename... Ts>
struct merge : Ts... {
template<typename... TTs>
merge(TTs&&... args) : Ts(std::forward<Ts>(args))... {}
using Ts::operator()...;
};
template<typename... Ts> merge(Ts&&...) -> merge<std::decay_t<Ts>...>;
And then, we could define generic like so:
auto generic = merge(first, second);
Now, generic is a variable of a function object with multiple call
operators. For std::visit, this is great:
std::variant<int, double> variant = 4.5;
auto result = std::visit(merge([](int a) { return a * 2; }, [](double a) ->
int { return std::floor(a); }), variant);
// result is 4
Instead of that generic lambda hack above, or even having to define a full
blown function object.
So, what do you think? The name could be better though :)
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/a916ba63-68b8-425c-8f10-9dff6487eca9%40isocpp.org.
------=_Part_2182_1715082683.1509647447121
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
<div dir=3D"ltr">I don't know if this is a good idea or not, and that&#=
39;s why I need other opinions.<div><br></div><div>It looks like it'd b=
e nice if we could have a simple non-verbose way to make a function object =
with multiple overloaded call operators. Currently, this is only possible b=
y either manually creating the function object (but this has the same drawb=
acks as function objects with only one call operator before lambdas were in=
troduced) or by simulating overload resolution:</div><div><br></div><div>au=
to first =3D [](int) { return 1; };</div><div>auto second =3D [](double) { =
return 1.5; };</div><div><br></div><div>auto generic =3D [&first, &=
second]<typename... Ts>(Ts&&... args) {<br>=C2=A0 =C2=A0 // n=
o references (or cv qualifiers) please</div><div>=C2=A0 =C2=A0 // this gets=
huge with function objects with different argument amount... :(</div><div>=
=C2=A0 =C2=A0 if constexpr(sizeof...(args) =3D=3D 1) {</div><div>=C2=A0 =C2=
=A0 =C2=A0 =C2=A0 if constexpr(std::is_convertible_v<std::decay_t<Ts&=
gt;..., int>)</div><div>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 first(=
std::forward<Ts>(args)...);</div><div>=C2=A0 =C2=A0 =C2=A0 =C2=A0 els=
e if constexpr(std::is_convertible_v<std::decay_t<Ts>..., double&g=
t;)</div><div>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 second(std::forward=
<Ts>(args)...);</div><div>=C2=A0 =C2=A0 =C2=A0 =C2=A0 else</div><div>=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 // static_assert(false) but with =
dependent condition...</div><div>=C2=A0 =C2=A0 }</div><div>=C2=A0 =C2=A0 el=
se</div><div>=C2=A0 =C2=A0 =C2=A0 =C2=A0 // static_assert(false) but with d=
ependent condition...</div><div>}</div><div><br></div><div>But even this so=
lution isn't perfect: The call wouldn't be ambiguous for a type tha=
t is convertible to both int and double, and to support that we need way mo=
re if conditions. And that's not good (and unmaintainable).</div><div>B=
ut unlike lambdas, I don't think that we should introduce yet another s=
yntax for lambdas with multiple call operators. Rather, I think it would be=
better if we introduce some sort of library class that would take care of =
merging function objects' call operators into a function object which h=
as every one of them. Here's how it could look like:</div><div><br></di=
v><div>template<typename... Ts></div><div>struct merge : Ts... {</div=
><div>=C2=A0 =C2=A0 template<typename... TTs></div><div>=C2=A0 =C2=A0=
merge(TTs&&... args) : Ts(std::forward<Ts>(args))... {}</div=
><div><br></div><div>=C2=A0 =C2=A0 using Ts::operator()...;</div><div>};</d=
iv><div><br></div><div>template<typename... Ts> merge(Ts&&...=
) -> merge<std::decay_t<Ts>...>;<br></div><div><br></div><di=
v>And then, we could define generic like so:</div><div><br></div><div>auto =
generic =3D merge(first, second);</div><div><br></div><div>Now, generic is =
a variable of a function object with multiple call operators. For std::visi=
t, this is great:</div><div><br></div><div>std::variant<int, double> =
variant =3D 4.5;</div><div>auto result =3D std::visit(merge([](int a) { ret=
urn a * 2; }, [](double a) -> int { return std::floor(a); }), variant);<=
/div><div>// result is 4</div><div><br></div><div>Instead of that generic l=
ambda hack above, or even having to define a full blown function object.</d=
iv><div>So, what do you think? The name could be better though :)</div></di=
v>
<p></p>
-- <br />
You received this message because you are subscribed to the Google Groups &=
quot;ISO C++ Standard - Future Proposals" group.<br />
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to <a href=3D"mailto:std-proposals+unsubscribe@isocpp.org">std-proposa=
ls+unsubscribe@isocpp.org</a>.<br />
To post to this group, send email to <a href=3D"mailto:std-proposals@isocpp=
..org">std-proposals@isocpp.org</a>.<br />
To view this discussion on the web visit <a href=3D"https://groups.google.c=
om/a/isocpp.org/d/msgid/std-proposals/a916ba63-68b8-425c-8f10-9dff6487eca9%=
40isocpp.org?utm_medium=3Demail&utm_source=3Dfooter">https://groups.google.=
com/a/isocpp.org/d/msgid/std-proposals/a916ba63-68b8-425c-8f10-9dff6487eca9=
%40isocpp.org</a>.<br />
------=_Part_2182_1715082683.1509647447121--
------=_Part_2181_1466724957.1509647447121--
.