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 &quot=
;template concept parameters&quot; or &quot;member concepts&quot; =E2=80=94=
 that is, either</div><div><br></div><div><font face=3D"courier new, monosp=
ace">=C2=A0 =C2=A0 template&lt;</font></div><div><font face=3D"courier new,=
 monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 template&lt;class&gt; concept Templ=
ateConceptParameter</font></div><div><font face=3D"courier new, monospace">=
=C2=A0 =C2=A0 &gt;</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&lt;class T&gt;</font></div><div=
><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 void bar=
() requires TemplateConceptParameter&lt;T&gt; {</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&lt;Inte=
gral&gt; 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(&quot;no&quot;); =C2=
=A0 =C2=A0 // error</font></div><div><br></div><div>or the poor man&#39;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&lt;class T&gt;</font><=
/div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0=
 concept MemberConcept =3D Integral&lt;T&gt;;</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&lt;</font></div><div><font face=3D"courier=
 new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 template&lt;class&gt; class X<=
/font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 &gt;</=
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&lt;class T&gt;</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&lt;T&gt; {</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&lt;integral_helper&gt; 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(&quot;no&quot;); =C2=A0 =C2=A0 // =
error</font></div><div><br></div><div>They were asked for a concrete use-ca=
se. Well, I&#39;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 &quot;subsumption&quot; =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&#39;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&lt;class T&gt;</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&lt;std::void_t&lt;T&gt;&gt;;</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&lt;class T&gt;</font></div><d=
iv><font face=3D"courier new, monospace">=C2=A0 =C2=A0 concept EvenMoreTrue=
 =3D AlwaysTrue&lt;T&gt; &amp;&amp;</font></div><div><font face=3D"courier =
new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 std::is_void_v&lt;std::void_t&l=
t;T, void&gt;&gt;;</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&lt;class Subsumes, class =3D int&gt;</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&lt;class S&gt=
;</font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 stru=
ct helper&lt;S, decltype(S::template f&lt;void&gt;())&gt;</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&lt;class&gt; concept A</font></div><div><font face=
=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 template&lt;class&g=
t; concept B</font></div><div><font face=3D"courier new, monospace">=C2=A0 =
=C2=A0 &gt;</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&lt;class T&gt; 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&lt;T&gt; || AlwaysTrue&lt;T&gt;;</font></di=
v><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 te=
mplate&lt;class T&gt; 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&lt;T&=
gt; || EvenMoreTrue&lt;T&gt;;</font></div><div><font face=3D"courier new, m=
onospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 using type =3D typename helper&lt;Sub=
sumes&gt;::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&lt;Int=
egral, Scalar&gt;);</font></div><div><font face=3D"courier new, monospace">=
=C2=A0 =C2=A0 static_assert(not Subsumes&lt;Scalar, Integral&gt;);</font></=
div><div><br></div><div>This works because if `A` subsumes `B`, then `A&lt;=
T&gt; || EvenMoreTrue&lt;T&gt;` will subsume `B&lt;T&gt; || AlwaysTrue&lt;T=
&gt;`,</div><div>and incidentally both constraints will *always be satisfie=
d*, even for `T=3Dvoid`, which is how we&#39;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&#39;t literally w=
rite</div><div><br></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&lt;class&gt; concept A</font></div><di=
v><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 templat=
e&lt;class&gt; concept B</font></div><div><font face=3D"courier new, monosp=
ace">=C2=A0 =C2=A0 &gt;</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&lt;class T&gt; static void f() \</font><=
/div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0=
 requires B&lt;T&gt; || AlwaysTrue&lt;T&gt;; \</font></div><div><font face=
=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 template&lt;class T&gt; st=
atic int f() \</font></div><div><font face=3D"courier new, monospace">=C2=
=A0 =C2=A0 =C2=A0 =C2=A0 requires A&lt;T&gt; || EvenMoreTrue&lt;T&gt;; \</f=
ont></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 u=
sing type =3D typename helper&lt;Subsumes##A##B&gt;::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&#39;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&quot; 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--

.