Path: csiph.com!eternal-september.org!feeder.eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail From: Tim Rentsch Newsgroups: comp.lang.c++ Subject: Re: Tricky! Date: Sat, 10 Aug 2019 14:19:00 -0700 Organization: A noiseless patient Spider Lines: 213 Message-ID: <86h86o90sb.fsf@mailhub.linuxsc.com> References: <7995a414-14ec-47ae-8afc-9ebcc520e30d@googlegroups.com> <86imrj8szn.fsf@mailhub.linuxsc.com> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Injection-Info: reader02.eternal-september.org; posting-host="4bad90a7c43721789c432acb03dbc8ee"; logging-data="17364"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1+CDIoLKkkAJFB7zxRTqAH4o5qNta7lbKU=" User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.4 (gnu/linux) Cancel-Lock: sha1:EkUf7Uzw4gXVjp15gXtX86Pw93o= sha1:UZF4MjupBVYyThmOqR3JwALUFpY= Xref: csiph.com comp.lang.c++:67534 "Alf P. Steinbach" writes: > On 30.07.2019 17:03, Tim Rentsch wrote: > >> "Alf P. Steinbach" writes: >> >>> On 29.07.2019 15:30, Tiib wrote: >>> >>>> On Monday, 29 July 2019 14:49:58 UTC+3, Bonita Montero wrote: >>>> >>>>> I need a class-function / method that returns a function-pointer >>>>> to the same type of class-function. How do I implement this? I >>>>> need this to have a kind of state-machine where each state-func- >>>>> tion returns a pointer to the next state function. I.e I would >>>>> write: "while( (fn = state->*fn()) != nullptr );". I see no way >>>>> to define the type of fn since it is recursive. >>>> >>>> Perhaps there are no way to make a function that return function >>>> pointer of its own type. However I do not understand the need >>>> of it. >>> >>> I think she is inspired by (possibly someone else who is inspired >>> by) an article by the Go language creator. >>> >>> As I recall his examples work nicely in Go, or assembly language, or >>> a more dynamic language like Python, but have to be rewritten with >>> some indirection for a language with strong static type checking >>> like C++. >> >> The problem here is not strong static type checking but the lack >> of recursive types in C++. A language can have recursive types >> and also have strong static type checking. >> >>> The reason for doing it that way, in languages where it's simple, is >>> the textual simplicity of the source code. Since it doesn't give >>> that textual simplicity in C++, it's not a good way in C++. It's >>> doable, just not in any practical way. >> >> That depends on what is meant by "practical". > > Short and clear source code and reasonably good efficiency. > > >> It can be done like this: >> >> #include >> >> class State { >> using Nexter = ...; >> >> public: >> State(){} >> Nexter r0(){ return &State::r1; } >> Nexter r1(){ return &State::r2; } >> Nexter r2(){ return &State::rn; } >> Nexter rn(){ return 0; } >> }; >> >> void >> run_state_machine(){ >> State *s = new State; >> >> for( auto f = &State::r0; f; f = (s->*f)() ){ >> ::std::cout << "Got one\n"; >> } >> } > > Yes, that's mainly the basic technique I had in mind: an extra level > of indirection, the solution to every computer science problem. Not a level of indirection, a layer of abstraction. Those aren't the same thing. The abstraction here is a five-minute exercise to write, ignorable in most of the defining class (that's one value of using an abstraction), completely invisible to client code, and incurs no run-time cost. So I think it meets the criteria you gave. > Nit-picks: (1) a State instance doesn't actually hold state, yet the > member functions are not `static`, and (2) there's a needless dynamic > allocation and memory leak. In cases like this where I am sketching out a solution to someone else's question, I assume without really thinking about it that people who read my comments are tracking enough to focus on those aspects that are relevant to the original problem, and not be distracted by incidental matters. Apparently that was a bad assumption here. Taking up your points in reverse order: (1) Yes the code above does a 'new' but not a 'delete'. That has no important bearing on the mechanism I was trying to explain. (2) We don't know that the dynamic allocation is needless, because we don't know what or how much member data the 'State' class might have in the context of the OP's question. (3) The member functions are regular member functions, because that's what the OP was asking about, and because, based on what question was asked, it seems obvious that the object in question is expected to have internal state (aka member data) that is manipulated by the various 'Nexter' member functions called within the object. > Fixing those issue, it can go like this (in practice one would have > some input argument to each function, so it can decide which state to > go to): > > > class State > { > struct Nexter > { > using Func = auto() -> Nexter; > Func* m_p_func; > > operator Func* () const { return m_p_func; } > Nexter( Func* const f ): m_p_func( f ) {} > }; > > State() = delete; > > public: > static auto r0() -> Nexter { return &State::r1; } > static auto r1() -> Nexter { return &State::r2; } > static auto r2() -> Nexter { return &State::rn; } > static auto rn() -> Nexter { return nullptr; } > }; > > #include > using namespace std; > > auto main() -> int > { > for( auto f = &State::r0; f; f = f() ) { > cout << "State " > << reinterpret_cast( f ) > << "." << endl; > } > } > > Here the reinterpret_cast is [...] I skipped over any questions about whether the cast would have problems since they aren't important to what you're explaining. The OP included a code fragment that clearly showed calling member functions through pointer-to-member-function return values. (This code fragment appears in a quoted portion above.) Is there some reason you're giving an example that doesn't conform to that interface? >> with a fairly short (five-ish lines) elaboration for the "...". > > Not sure what you're thinking of there? Much like yours but using pointer-to-member-function rather than pointer-to-function, to wit: using Nexter = struct Hidden { using PMF = Hidden (State::*)(); Hidden( PMF p0 ) : p( p0 ) {} operator PMF(){ return p; } PMF p; }; >> Personally I think this approach offers a fairly nice textual >> simplicity, and is eminently practical. >> >> If someone thinks five lines of overhead is too much, the "..." >> can be made into a template, giving >> >> using Nexter = WrapMemberFunctionPointer< State >; >> >> which provides an even nicer source text. > > Well, to my mind the indirection adds complexity so that even though > the code is short it's not IMO /clear/, I don't think of what I suggested as an indirection, but as an abstraction. A principal value of abstraction is to hide complexity by tucking it away somewhere so it can be ignored afterwards. Here I think that virtue is achieved rather nicely. If someone were to think of a scheme along these lines not as an abstraction but as an indirection, and one which is not meant to hide inner workings, that could very well be confusing. I don't know why someone would want to think of this approach that way. > and for debugging it's not so > clear which state one is in, just by looking at a function address. I think how you are conceptualizing the problem is not the same as how the OP is conceptualizing the problem. From how the question was asked I inferred that 'State' is not just the integer state of a finite state machine, but something more elaborate (and perhaps much more elaborate) that knows what to do to accomplish state transitions as well as which state should be "gone to". And there may not be a one-to-one mapping between 'Nexter' member functions and states of the FSM that the 'State' object is in; that information may not even be available from just knowing what the pointer value is. If we want to know something about what state the object is in, presumably we should ask the object using some other (not yet stated) member function. > But, subjective opinion. ;-) I suspect your opinion here is shaped largely by how you are framing the question. I took the OP at face value that what was being asked for is in fact what is wanted. It's interesting that in this thread three different people have suggested solutions to problems other than what OP was asking about.