C++ Logo

std-proposals

Advanced search

Re: Generic template 'this' + new for_each_member() template template functor

From: Phil Bouchard <phil_at_[hidden]>
Date: Tue, 10 Mar 2020 00:03:57 -0400
Here's a prettier example, without the template template functor:

#include <iostream>
#include <functional>

using namespace std;


// Compiler internal:

template <typename F, typename ...T>
     struct for_each_member;

template <typename F, typename ...T>
     inline void make_for_each_member(F && f, T && ...t)
     {
         for_each_member<F, T...>()(forward<F>(f), forward<T>(t)...);
     }


// New "pretty_print" header:

struct pretty_print
{
     size_t n;

     pretty_print(size_t n = 0):n(n) {}

     ostream & indent(size_t n, ostream & out)
     {
         for (size_t i = 0; i < n * 4; ++ i)
             out << ' ';

         return out;
     }

     template <typename T>
         void operator () (T const & t, size_t i, size_t e, size_t n,
ostream & out)
         {
             if (i == 0)
                 indent(n, out) << '{' << endl;

             indent(n + 1, out) << t;

             if (i != e - 1)
                 out << ',';

             out << endl;

             if (i == e - 1)
                 indent(n, out) << '}';
         }
};

template <typename T>
     inline ostream & T::pretty_print(size_t n, ostream & out)
     {
         pretty_print f(n);

         make_for_each_member<pretty_print>(f, T(), cout);

         return out;
     }


// Usage example:

struct A
{
     int a = 0, b = 1, c = 2;

     // Capabilities:
     ostream & pretty_print(size_t n, ostream & out); // only a
declaration needed if "pretty_print" header included
     ostream & binary_print(ostream & out); // same here
     ostream & sql_print(ostream & out); // same
};


// Generated by the compiler:

template <typename F, typename ...T>
     struct for_each_member<F, A, T...>
     {
         void operator () (F && f, A && a, T && ...t)
         {
             f(forward<decltype(a.a)>(a.a), 0, 3, forward<T>(t)...);
             f(forward<decltype(a.b)>(a.b), 1, 3, forward<T>(t)...);
             f(forward<decltype(a.c)>(a.c), 2, 3, forward<T>(t)...);
         }
     };


int main()
{
     A a;

     a.pretty_print(0, cout) << endl;
}

-- 
*Phil Bouchard*
Founder
C.: (819) 328-4743
Fornux Logo <http://www.fornux.com>
On 3/9/20 8:44 PM, Phil Bouchard wrote:
>
> Here is a clean usage example:
>
> #include <iostream>
> #include <functional>
>
> using namespace std;
>
>
> // Compiler internal:
>
> template <template <typename> class F, typename ...T> // note the 
> usage of the necessary template template parameter
>     struct for_each_member;
>
> template <template <typename> class F, typename ...T>
>     inline void make_for_each_member(T && ...t)
>     {
>         for_each_member<F, T...>()(forward<T>(t)...);
>     }
>
>
> // New "pretty_print" header:
>
> template <typename T>
>     struct pretty_print
>     {
>         ostream & indent(size_t n, ostream & out)
>         {
>             for (size_t i = 0; i < n * 4; ++ i)
>                 out << ' ';
>
>             return out;
>         }
>
>         void operator () (T const & t, size_t i, size_t e, size_t n, 
> ostream & out)
>         {
>             if (i == 0)
>                 indent(n, out) << '{' << endl;
>
>             indent(n + 1, out) << t;
>
>             if (i != e - 1)
>                 out << ',';
>
>             out << endl;
>
>             if (i == e - 1)
>                 indent(n, out) << '}';
>         }
>     };
>
> template <typename T>
>     inline ostream & T::pretty_print(ostream & out)
>     {
>         size_t indent = 0;
>
>         make_for_each_member<pretty_print>(T(), indent, cout);
>
>         return out;
>     }
>
>
> // Usage example (what the user will only see):
>
> struct A
> {
>     int a = 0, b = 1, c = 2;
>
>     ostream & pretty_print(ostream & out); // only a declaration 
> needed if "pretty_print" header included
> };
>
>
> // Generated by the compiler:
>
> template <template <typename> class F, typename ...T>
>     struct for_each_member<F, A, T...>
>     {
>         void operator () (A && a, T && ...t)
>         {
> F<decltype(a.a)>()(forward<decltype(a.a)>(a.a), 0, 3, forward<T>(t)...);
> F<decltype(a.b)>()(forward<decltype(a.b)>(a.b), 1, 3, forward<T>(t)...);
> F<decltype(a.c)>()(forward<decltype(a.c)>(a.c), 2, 3, forward<T>(t)...);
>         }
>     };
>
>
> int main()
> {
>     A a;
>
>     a.pretty_print(cout) << endl;
> }
>
>
> Other applications of for_each_member() can be:
>
> - comparisons,
>
> - changing properties of filtered mutable member variables,
>
> - generic debug functions changed on-the-fly at one place only,
>
> - easily apply textual and binary I/O operations to huge structures,
>
> - easily apply database operations to huge structures,
>
> - automate the mapping of structures to a user-interface generator,
>
> - ...
>
>
> -- 
>
> *Phil Bouchard*
> Founder
> C.: (819) 328-4743
>
> Fornux Logo <http://www.fornux.com>
>
>
> On 3/9/20 10:56 AM, Phil Bouchard wrote:
>> ... Also we could limit generic ‘this’ overloads to specific namespaces:
>>
>> template <typename Class>
>>     ostream & boost::detail::Class::operator << (ostream & out) { ... }
>>
>> -- 
>>
>> *Phil Bouchard*
>> Founder
>> C.: (819) 328-4743 <tel:(819)%20328-4743>
>>
>> Fornux Logo <http://www.fornux.com/>
>>
>> On Mar 8, 2020, at 7:36 PM, Phil Bouchard <phil_at_[hidden] 
>> <mailto:phil_at_[hidden]>> wrote:
>>
>>> ... And only the classes with the proper declarations would take 
>>> advantage of the generic template 'this':
>>>
>>>
>>> -- 
>>>
>>> *Phil Bouchard*
>>> Founder
>>> C.: (819) 328-4743
>>>
>>> Fornux Logo <http://www.fornux.com>
>>>
>>>
>>> On 3/8/20 7:34 PM, Phil Bouchard wrote:
>>>>
>>>> What I have in mind is to clean up the dispatch fiasco that is done 
>>>> with either the constructors or the cast operator right now. 
>>>> Conversions, comparisons, ... should be simplified as well. For 
>>>> example:
>>>>
>>>> struct X
>>>>
>>>> {
>>>>
>>>>     int value;
>>>>
>>>>
>>>>     template <typename T>
>>>>
>>>>         X(T const & T);
>>>>
>>>>     template <typename T>
>>>>
>>>>         operator T ();
>>>>
>>>> };
>>>>
>>>> struct Y
>>>>
>>>> {
>>>>
>>>>     int value;
>>>>
>>>>
>>>>     template <typename T>
>>>>
>>>>         Y(T const & T);
>>>>
>>>>     template <typename T>
>>>>
>>>>         operator T ();
>>>>
>>>> };
>>>>
>>>> template <typename Class, typename T>
>>>>
>>>>     inline Class::operator T () { return T(* this); } // generic 
>>>> constructor wrapper
>>>>
>>>> template <typename Class, typename T>
>>>>
>>>>     inline Class::Class(T const &t) { value = t.value; } // cast 
>>>> operation really done here
>>>>
>>>>
>>>> And normal specializations here:
>>>>
>>>> template <typename T>
>>>>
>>>>     inline X::X(T const &t) { value = t.value; } // specialized 
>>>> cast operation done here
>>>>
>>>>
>>>> Also if we had a way to iterate all members (which would be another 
>>>> important proposal) then we could also have a generic "operator <<" 
>>>> as well:
>>>>
>>>> template <typename Class>
>>>>
>>>>     ostream & Class::operator << (ostream & out)
>>>>
>>>>     {
>>>>
>>>>         for (member_iterator<Class> i = 
>>>> member_iterator<Class>::begin(); i != 
>>>> member_iterator<Class>::end(); ++ i)
>>>>
>>>>         {
>>>>
>>>>             if (i != member_iterator<Class>::begin())
>>>>
>>>>                 out << ", ";
>>>>
>>>>             out << this->*i;
>>>>
>>>>         }
>>>>
>>>>         return out;
>>>>
>>>>     }
>>>>
>>>> The aforementioned should also remove the need for these global 
>>>> friend function overloads.
>>>>
>>>>
>>>> -- 
>>>>
>>>> *Phil Bouchard*
>>>> Founder
>>>> C.: (819) 328-4743
>>>>
>>>> Fornux Logo <http://www.fornux.com>
>>>>
>>>>
>>>> On 3/8/20 6:03 PM, Jake Arkinstall wrote:
>>>>> For me, this approach has some merits, but this particular example 
>>>>> is something that, to me, makes much more sense as a free function.
>>>>>
>>>>> Do you have further examples?
>>>>>
>>>>> On Sun, 8 Mar 2020, 21:55 Phil Bouchard via Std-Proposals, 
>>>>> <std-proposals_at_[hidden] 
>>>>> <mailto:std-proposals_at_[hidden]>> wrote:
>>>>>
>>>>>     Well I believe a proposal should be open for debate, just like
>>>>>     a thesis.
>>>>>
>>>>>     We both agree these overloads should be simplified but we
>>>>>     disagree on the syntax.
>>>>>
>>>>>     I saw the recursive lambda thing and I think I already talked
>>>>>     to you before regarding CV overloads but we were proposing the
>>>>>     following syntax:
>>>>>
>>>>>     struct A
>>>>>
>>>>>     {
>>>>>
>>>>>         template <bool BC>
>>>>>
>>>>>             void foo() bool<BC>
>>>>>
>>>>>             {
>>>>>
>>>>>             }
>>>>>
>>>>>     };
>>>>>
>>>>>     And regarding the this generic overloading I still believe the
>>>>>     following syntax is cleaner:
>>>>>
>>>>>     struct A
>>>>>
>>>>>     {
>>>>>
>>>>>         template <typename T>
>>>>>
>>>>>             bool compare(T const &);
>>>>>
>>>>>     };
>>>>>
>>>>>     template <typename C, typename T>
>>>>>
>>>>>         void C::compare(C const &)
>>>>>
>>>>>         {
>>>>>
>>>>>             ...
>>>>>
>>>>>         }
>>>>>
>>>>>     Because with your syntax I could technically reassign "self":
>>>>>
>>>>>     struct A
>>>>>
>>>>>     {
>>>>>
>>>>>         template <typename C, typename T>
>>>>>
>>>>>             bool compare(this C & c, T const &);
>>>>>
>>>>>     };
>>>>>
>>>>>     template <typename C, typename T>
>>>>>
>>>>>         bool C::compare(this C & c, T const &) // redundant C here
>>>>>
>>>>>         {
>>>>>
>>>>>             static C sc = C();
>>>>>
>>>>>             c = sc; // in theory we could do this unless you patch
>>>>>     the compilers with a new error for that specific case
>>>>>
>>>>>         }
>>>>>
>>>>>
>>>>>     -- 
>>>>>
>>>>>     *Phil Bouchard*
>>>>>     Founder
>>>>>     C.: (819) 328-4743
>>>>>
>>>>>     Fornux Logo <http://www.fornux.com>
>>>>>
>>>>>
>>>>>     On 3/8/20 5:37 AM, Gašper Ažman wrote:
>>>>>>     I meant that you should consider the incremental value of
>>>>>>     your proposal over p0847, which is on track for c++23 unless
>>>>>>     serious issues crop up
>>>>>>
>>>>>>     On Sat, Mar 7, 2020, 19:04 Phil Bouchard <phil_at_[hidden]
>>>>>>     <mailto:phil_at_[hidden]>> wrote:
>>>>>>
>>>>>>         The syntaxes are way different:
>>>>>>
>>>>>>         http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0847r4.html
>>>>>>
>>>>>>         In your case the compiler will have to change all of its
>>>>>>         rules for the number of parameters "operator" overloads.
>>>>>>         For example:
>>>>>>
>>>>>>         struct A
>>>>>>
>>>>>>         {
>>>>>>
>>>>>>             int value;
>>>>>>
>>>>>>             ...
>>>>>>
>>>>>>             template <typename Self>
>>>>>>
>>>>>>                 bool operator < (this Self && self, A const & a)
>>>>>>         // 2 parameters... ok
>>>>>>
>>>>>>                 {
>>>>>>
>>>>>>                     return self.value < a.value;
>>>>>>
>>>>>>                 }
>>>>>>
>>>>>>
>>>>>>                 bool operator < (A const & a) // 1 parameter... ok
>>>>>>
>>>>>>                 {
>>>>>>
>>>>>>                     return value < a.value;
>>>>>>
>>>>>>                 }
>>>>>>
>>>>>>         };
>>>>>>
>>>>>>
>>>>>>         -- 
>>>>>>
>>>>>>         *Phil Bouchard*
>>>>>>         Founder
>>>>>>         C.: (819) 328-4743
>>>>>>
>>>>>>         Fornux Logo <http://www.fornux.com>
>>>>>>
>>>>>>
>>>>>>         On 3/7/20 1:35 PM, Gašper Ažman wrote:
>>>>>>>         P0847
>>>>>>>
>>>>>>>         On Sat, Mar 7, 2020, 18:33 Phil Bouchard via
>>>>>>>         Std-Proposals <std-proposals_at_[hidden]
>>>>>>>         <mailto:std-proposals_at_[hidden]>> wrote:
>>>>>>>
>>>>>>>             Alright, I'm pretty sure this is not implemented
>>>>>>>             yet. Suppose you have:
>>>>>>>
>>>>>>>             struct A
>>>>>>>
>>>>>>>             {
>>>>>>>
>>>>>>>                 int value;
>>>>>>>
>>>>>>>                 ...
>>>>>>>
>>>>>>>                 template <typename T>
>>>>>>>
>>>>>>>                     int compare(T const & t) const;
>>>>>>>
>>>>>>>             }
>>>>>>>
>>>>>>>
>>>>>>>             struct B
>>>>>>>
>>>>>>>             {
>>>>>>>
>>>>>>>                 int value;
>>>>>>>
>>>>>>>                 ...
>>>>>>>
>>>>>>>                 template <typename T>
>>>>>>>
>>>>>>>                     int compare(T const & t) const;
>>>>>>>
>>>>>>>             }
>>>>>>>
>>>>>>>
>>>>>>>             Then a generic way to define the same functionality
>>>>>>>             for all classes would be to have a "template 'this'":
>>>>>>>
>>>>>>>             template <typename C, typename T>
>>>>>>>
>>>>>>>                     inline int C::compare(T const & t) const
>>>>>>>
>>>>>>>                     {
>>>>>>>
>>>>>>>                         return value == t.value ? 0 : value <
>>>>>>>             t.value ? -1 : 1;
>>>>>>>
>>>>>>>                     }
>>>>>>>
>>>>>>>
>>>>>>>             (Please include my email address in your replies)
>>>>>>>
>>>>>>>
>>>>>>>             -- 
>>>>>>>
>>>>>>>             *Phil Bouchard*
>>>>>>>             Founder
>>>>>>>             C.: (819) 328-4743
>>>>>>>
>>>>>>>             Fornux Logo <http://www.fornux.com>
>>>>>>>             -- 
>>>>>>>             Std-Proposals mailing list
>>>>>>>             Std-Proposals_at_[hidden]
>>>>>>>             <mailto:Std-Proposals_at_[hidden]>
>>>>>>>             https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>>>>>>
>>>>>     -- 
>>>>>     Std-Proposals mailing list
>>>>>     Std-Proposals_at_[hidden]
>>>>>     <mailto:Std-Proposals_at_[hidden]>
>>>>>     https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>>>>

Received on 2020-03-09 23:06:45