Topic: A use-case for member concepts and/or template
Author: Arthur O'Dwyer <arthur.j.odwyer@gmail.com>
Date: Mon, 17 Sep 2018 16:46:45 -0700 (PDT)
Raw View
------=_Part_1924_1231457910.1537228005731
Content-Type: multipart/alternative;
boundary="----=_Part_1925_569327165.1537228005731"
------=_Part_1925_569327165.1537228005731
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
https://quuxplusone.github.io/blog/2018/09/17/i-can-haz-member-concepts/
A while back, someone on the C++ Slack channel asked whether it was=20
possible to have
either "template concept parameters" or "member concepts" =E2=80=94 that is=
, either
template<
template<class> concept TemplateConceptParameter
>
struct Foo {
template<class T>
void bar() requires TemplateConceptParameter<T> {
// ...
}
};
Foo<Integral> foo;
foo.bar(1); // OK
foo.bar("no"); // error
or the poor man's version,
struct integral_helper {
template<class T>
concept MemberConcept =3D Integral<T>;
};
template<
template<class> class X
>
struct Foo {
template<class T>
void bar() requires X::template MemberConcept<T> {
// ...
}
};
Foo<integral_helper> foo;
foo.bar(1); // OK
foo.bar("no"); // error
They were asked for a concrete use-case. Well, I've got a concrete use-case=
=20
now,
albeit kind of ivory-tower.
I want to write a little snippet of C++ code to illustrate the C++2a notion=
=20
of "subsumption" =E2=80=94
how one concept can refine another concept in a way understandable by the=
=20
compiler.
The rules for subsumption are quite persnickety: sometimes the compiler=20
understands
that concept `A` subsumes `B`, and sometimes it doesn't.
For any *given* pair of concepts `A` and `B`, we can test their=20
relationship to each
other by creating a pair of functions, one constrained on `A` and the other=
=20
constrained
on `B`, and then seeing whether that makes overload resolution happy or sad=
..
So we write: <https://godbolt.org/z/wgHfIh>
template<class T>
concept AlwaysTrue =3D
std::is_void_v<std::void_t<T>>;
template<class T>
concept EvenMoreTrue =3D AlwaysTrue<T> &&
std::is_void_v<std::void_t<T, void>>;
template<class Subsumes, class =3D int>
struct helper : std::false_type {};
template<class S>
struct helper<S, decltype(S::template f<void>())>
: std::true_type {};
template<
template<class> concept A
template<class> concept B
>
struct Subsumes {
template<class T> static void f()
requires B<T> || AlwaysTrue<T>;
template<class T> static int f()
requires A<T> || EvenMoreTrue<T>;
using type =3D typename helper<Subsumes>::type;
static constexpr bool value =3D type::value;
};
static_assert(Subsumes<Integral, Scalar>);
static_assert(not Subsumes<Scalar, Integral>);
This works because if `A` subsumes `B`, then `A<T> || EvenMoreTrue<T>` will=
=20
subsume `B<T> || AlwaysTrue<T>`,
and incidentally both constraints will *always be satisfied*, even for=20
`T=3Dvoid`, which is how we're going
to instantiate `f()`.
So this snippet works great <https://godbolt.org/z/wgHfIh> to verify that=
=20
subsumption works the way I expect it to.
The only problem is that we can't literally write
template<
template<class> concept A
template<class> concept B
>
Instead, we have to fall back on Boost-era macro metaprogramming:
#define DEFINE_SUBSUMES(A,B) \
struct Subsumes##A##B { \
template<class T> static void f() \
requires B<T> || AlwaysTrue<T>; \
template<class T> static int f() \
requires A<T> || EvenMoreTrue<T>; \
using type =3D typename helper<Subsumes##A##B>::type; \
static constexpr bool value =3D type::value; \
};
DEFINE_SUBSUMES(Scalar, Integral)
DEFINE_SUBSUMES(Integral, Scalar)
DEFINE_SUBSUMES(NonScalar, NonIntegral)
DEFINE_SUBSUMES(NonIntegral, NonScalar)
static_assert(SubsumesIntegralScalar::value);
static_assert(not SubsumesScalarIntegral::value);
static_assert(not SubsumesNonScalarNonIntegral::value);
static_assert(not SubsumesNonIntegralNonScalar::value);
Is this a motivating enough example to get member concepts into C++2a?
Or merely motivating enough to get subsumption *out?* ;)
Honestly I'd accept either one as a step forward.
=E2=80=93Arthur
--=20
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 e=
mail 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/8ed8920e-8e57-407d-89bb-cfe4716858f0%40isocpp.or=
g.
------=_Part_1925_569327165.1537228005731
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
<div dir=3D"ltr"><div><a href=3D"https://quuxplusone.github.io/blog/2018/09=
/17/i-can-haz-member-concepts/">https://quuxplusone.github.io/blog/2018/09/=
17/i-can-haz-member-concepts/</a><br class=3D"Apple-interchange-newline" st=
yle=3D"caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: -webkit=
-standard;"></div><div><br></div><div><div>A while back, someone on the C++=
Slack channel asked whether it was possible to have</div><div>either "=
;template concept parameters" or "member concepts" =E2=80=94=
that is, either</div><div><br></div><div><font face=3D"courier new, monosp=
ace">=C2=A0 =C2=A0 template<</font></div><div><font face=3D"courier new,=
monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 template<class> concept Templ=
ateConceptParameter</font></div><div><font face=3D"courier new, monospace">=
=C2=A0 =C2=A0 ></font></div><div><font face=3D"courier new, monospace">=
=C2=A0 =C2=A0 struct Foo {</font></div><div><font face=3D"courier new, mono=
space">=C2=A0 =C2=A0 =C2=A0 =C2=A0 template<class T></font></div><div=
><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 void bar=
() requires TemplateConceptParameter<T> {</font></div><div><font face=
=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 // ..=
..</font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=
=A0 =C2=A0 }</font></div><div><font face=3D"courier new, monospace">=C2=A0 =
=C2=A0 };</font></div><div><font face=3D"courier new, monospace"><br></font=
></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 Foo<Inte=
gral> foo;</font></div><div><font face=3D"courier new, monospace">=C2=A0=
=C2=A0 foo.bar(1); =C2=A0 =C2=A0 =C2=A0 =C2=A0// OK</font></div><div><font=
face=3D"courier new, monospace">=C2=A0 =C2=A0 foo.bar("no"); =C2=
=A0 =C2=A0 // error</font></div><div><br></div><div>or the poor man's v=
ersion,</div><div><br></div><div><font face=3D"courier new, monospace">=C2=
=A0 =C2=A0 struct integral_helper {</font></div><div><font face=3D"courier =
new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 template<class T></font><=
/div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0=
concept MemberConcept =3D Integral<T>;</font></div><div><font face=
=3D"courier new, monospace">=C2=A0 =C2=A0 };</font></div><div><font face=3D=
"courier new, monospace"><br></font></div><div><font face=3D"courier new, m=
onospace">=C2=A0 =C2=A0 template<</font></div><div><font face=3D"courier=
new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 template<class> class X<=
/font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 ></=
font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 struct =
Foo {</font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =
=C2=A0 =C2=A0 template<class T></font></div><div><font face=3D"courie=
r new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 void bar() requires X::templa=
te MemberConcept<T> {</font></div><div><font face=3D"courier new, mon=
ospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 // ...</font></div><div><=
font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 }</font></=
div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 };</font></div=
><div><font face=3D"courier new, monospace"><br></font></div><div><font fac=
e=3D"courier new, monospace">=C2=A0 =C2=A0 Foo<integral_helper> foo;<=
/font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 foo.ba=
r(1); =C2=A0 =C2=A0 =C2=A0 =C2=A0// OK</font></div><div><font face=3D"couri=
er new, monospace">=C2=A0 =C2=A0 foo.bar("no"); =C2=A0 =C2=A0 // =
error</font></div><div><br></div><div>They were asked for a concrete use-ca=
se. Well, I've got a concrete use-case now,</div><div>albeit kind of iv=
ory-tower.</div><div><br></div><div>I want to write a little snippet of C++=
code to illustrate the C++2a notion of "subsumption" =E2=80=94</=
div><div>how one concept can refine another concept in a way understandable=
by the compiler.</div><div>The rules for subsumption are quite persnickety=
: sometimes the compiler understands</div><div>that concept `A` subsumes `B=
`, and sometimes it doesn't.</div><div><br></div><div>For any *given* p=
air of concepts `A` and `B`, we can test their relationship to each</div><d=
iv>other by creating a pair of functions, one constrained on `A` and the ot=
her constrained</div><div>on `B`, and then seeing whether that makes overlo=
ad resolution happy or sad.</div><div><br></div><div><a href=3D"https://god=
bolt.org/z/wgHfIh">So we write:</a></div><div><br></div><div><font face=3D"=
courier new, monospace">=C2=A0 =C2=A0 template<class T></font></div><=
div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 concept AlwaysTrue =
=3D</font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =
=C2=A0 =C2=A0 std::is_void_v<std::void_t<T>>;</font></div><div>=
<font face=3D"courier new, monospace"><br></font></div><div><font face=3D"c=
ourier new, monospace">=C2=A0 =C2=A0 template<class T></font></div><d=
iv><font face=3D"courier new, monospace">=C2=A0 =C2=A0 concept EvenMoreTrue=
=3D AlwaysTrue<T> &&</font></div><div><font face=3D"courier =
new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 std::is_void_v<std::void_t&l=
t;T, void>>;</font></div><div><font face=3D"courier new, monospace"><=
br></font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 te=
mplate<class Subsumes, class =3D int></font></div><div><font face=3D"=
courier new, monospace">=C2=A0 =C2=A0 struct helper : std::false_type {};</=
font></div><div><font face=3D"courier new, monospace"><br></font></div><div=
><font face=3D"courier new, monospace">=C2=A0 =C2=A0 template<class S>=
;</font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 stru=
ct helper<S, decltype(S::template f<void>())></font></div><div>=
<font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 : std::tr=
ue_type {};</font></div><div><font face=3D"courier new, monospace"><br></fo=
nt></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 template&=
lt;</font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =
=C2=A0 =C2=A0 template<class> concept A</font></div><div><font face=
=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 template<class&g=
t; concept B</font></div><div><font face=3D"courier new, monospace">=C2=A0 =
=C2=A0 ></font></div><div><font face=3D"courier new, monospace">=C2=A0 =
=C2=A0 struct Subsumes {</font></div><div><font face=3D"courier new, monosp=
ace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 template<class T> static void f()</f=
ont></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 requires B<T> || AlwaysTrue<T>;</font></di=
v><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 te=
mplate<class T> static int f()</font></div><div><font face=3D"courier=
new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 requires A<T&=
gt; || EvenMoreTrue<T>;</font></div><div><font face=3D"courier new, m=
onospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 using type =3D typename helper<Sub=
sumes>::type;</font></div><div><font face=3D"courier new, monospace">=C2=
=A0 =C2=A0 =C2=A0 =C2=A0 static constexpr bool value =3D type::value;</font=
></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 };</font></=
div><div><font face=3D"courier new, monospace"><br></font></div><div><font =
face=3D"courier new, monospace">=C2=A0 =C2=A0 static_assert(Subsumes<Int=
egral, Scalar>);</font></div><div><font face=3D"courier new, monospace">=
=C2=A0 =C2=A0 static_assert(not Subsumes<Scalar, Integral>);</font></=
div><div><br></div><div>This works because if `A` subsumes `B`, then `A<=
T> || EvenMoreTrue<T>` will subsume `B<T> || AlwaysTrue<T=
>`,</div><div>and incidentally both constraints will *always be satisfie=
d*, even for `T=3Dvoid`, which is how we're going</div><div>to instanti=
ate `f()`.</div><div><br></div><div>So this snippet <a href=3D"https://godb=
olt.org/z/wgHfIh">works great</a> to verify that subsumption works the way =
I expect it to.</div><div>The only problem is that we can't literally w=
rite</div><div><br></div><div><font face=3D"courier new, monospace">=C2=A0 =
=C2=A0 template<</font></div><div><font face=3D"courier new, monospace">=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 template<class> concept A</font></div><di=
v><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 templat=
e<class> concept B</font></div><div><font face=3D"courier new, monosp=
ace">=C2=A0 =C2=A0 ></font></div><div><br></div><div>Instead, we have to=
fall back on Boost-era macro metaprogramming:</div><div><br></div><div><fo=
nt face=3D"courier new, monospace">=C2=A0 =C2=A0 #define DEFINE_SUBSUMES(A,=
B) \</font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 s=
truct Subsumes##A##B { \</font></div><div><font face=3D"courier new, monosp=
ace">=C2=A0 =C2=A0 =C2=A0 template<class T> static void f() \</font><=
/div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0=
requires B<T> || AlwaysTrue<T>; \</font></div><div><font face=
=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 template<class T> st=
atic int f() \</font></div><div><font face=3D"courier new, monospace">=C2=
=A0 =C2=A0 =C2=A0 =C2=A0 requires A<T> || EvenMoreTrue<T>; \</f=
ont></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 u=
sing type =3D typename helper<Subsumes##A##B>::type; \</font></div><d=
iv><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 static conste=
xpr bool value =3D type::value; \</font></div><div><font face=3D"courier ne=
w, monospace">=C2=A0 =C2=A0 };</font></div><div><font face=3D"courier new, =
monospace"><br></font></div><div><font face=3D"courier new, monospace">=C2=
=A0 =C2=A0 DEFINE_SUBSUMES(Scalar, Integral)</font></div><div><font face=3D=
"courier new, monospace">=C2=A0 =C2=A0 DEFINE_SUBSUMES(Integral, Scalar)</f=
ont></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 DEFINE_S=
UBSUMES(NonScalar, NonIntegral)</font></div><div><font face=3D"courier new,=
monospace">=C2=A0 =C2=A0 DEFINE_SUBSUMES(NonIntegral, NonScalar)</font></d=
iv><div><font face=3D"courier new, monospace"><br></font></div><div><font f=
ace=3D"courier new, monospace">=C2=A0 =C2=A0 static_assert(SubsumesIntegral=
Scalar::value);</font></div><div><font face=3D"courier new, monospace">=C2=
=A0 =C2=A0 static_assert(not SubsumesScalarIntegral::value);</font></div><d=
iv><font face=3D"courier new, monospace">=C2=A0 =C2=A0 static_assert(not Su=
bsumesNonScalarNonIntegral::value);</font></div><div><font face=3D"courier =
new, monospace">=C2=A0 =C2=A0 static_assert(not SubsumesNonIntegralNonScala=
r::value);</font></div><div><br></div><div>Is this a motivating enough exam=
ple to get member concepts into C++2a?</div><div>Or merely motivating enoug=
h to get subsumption *out?* ;)</div><div>Honestly I'd accept either one=
as a step forward.</div></div><div><br></div><div>=E2=80=93Arthur</div></d=
iv>
<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/8ed8920e-8e57-407d-89bb-cfe4716858f0%=
40isocpp.org?utm_medium=3Demail&utm_source=3Dfooter">https://groups.google.=
com/a/isocpp.org/d/msgid/std-proposals/8ed8920e-8e57-407d-89bb-cfe4716858f0=
%40isocpp.org</a>.<br />
------=_Part_1925_569327165.1537228005731--
------=_Part_1924_1231457910.1537228005731--
.