0

I've got a member-function that need to access both member data and "local" data:

struct S final
{
   int i = 123;
   // ... a bunch of legacy code ...
   void f()
   {
      // ... a bunch of legacy code ...
      double d = 1.23;
      // ... a bunch of legacy code ...
      g(d);
      // ... a bunch of legacy code ...
   }
   // ... a bunch of legacy code ...
   void g(double& d)
   {
      i = 456; d = 4.56;
   }
};

This, of course, works ... however, it becomes a nuisance as more variables local to f() are passed to g(). Is there an 'easy' way to avoid this?

Using a lambda is the "stock" answer, however that means the code for g() must be moved to be part of f(); I don't want to do that:

struct S final
{
   int i = 123;
   void f()
   {
      double d = 1.23;
      auto g = [&]() { i = 456; d = 4.56; } // code moved here :-(
      g(d);
   }
};

Something like this is close (although the lambda is "better" as local variables are part of the closure with [&]), but it's not valid C++

struct S final
{
   int i = 123;
   void f()
   {
      double d = 1.23;
      void g();
      g();
   }
   void f::g()
   {
      i = 456; d = 4.56;
   }
};

Or (again, using fictional syntax) a way to declare a lambda and then define it later:

struct S final
{
   int i = 123;
   void f()
   {
      double d = 1.23;
      auto g = [&]();
      g();
   
      g = { i = 456; d = 4.56; }
    }
};
Jack Brown
  • 63
  • 6
  • 1
    "*that means the code for g() must be moved to be part of f(); I don't want to do that:*" Then it's not a "local function," is it? A function has no right to access variables outside of its *visual* scope. That's one of the core tenants of structured programming. Overall, when you're trying to break how structured programming is meant to work, that's an indicator that you're doing something wrong in your code structure or design. – Nicol Bolas Jul 26 '22 at 14:23
  • Have you tried using parameter pack? – Karen Baghdasaryan Jul 26 '22 at 14:24
  • it seem you actually dont want the function to be "local". Why is it not a (private) member function? – 463035818_is_not_an_ai Jul 26 '22 at 14:25
  • @JackBrown: "*I still have to name the variables when calling g()*" You didn't have to when you turned it into a lambda. – Nicol Bolas Jul 26 '22 at 14:26
  • a function that has too many arguments is a problem of design, and imho you are fighting it at the wrong end. What are the many arguments? Can you group them into data structures? Can you refactor to make `g` a member of a new class with some of that many arguments as members? There are many ways, but trying to do magic with syntax isnt the right way imho – 463035818_is_not_an_ai Jul 26 '22 at 14:29
  • @JackBrown: I understand what you want. My point is that what you want is *nonsensical*. It represents dysfunctional code organization. "*it's legacy code.*" That's doesn't make it less of an attempt to defeat a core aspect of structured programming. – Nicol Bolas Jul 26 '22 at 14:30
  • Local struct? `void f() {struct {double d = 1.23; void g() {...}} _; _.g();}` – user253751 Jul 26 '22 at 14:31
  • @JackBrown what about making `d` a member variable? You know that it is ugly, but you care about reducing code changes, more than making well-designed code – user253751 Jul 26 '22 at 14:36
  • the lambda capture is shorthand notion to define a type (and then an instance of it) with corresponding members. `auto g = [&]();` in a different scope will define a different type (even if we disregard for a moment that every lambda expression is of different type). – 463035818_is_not_an_ai Jul 26 '22 at 14:39
  • There is nothing that can capture all variables in different scopes (at least nothing I am aware of). Maybe you are looking for a macro – 463035818_is_not_an_ai Jul 26 '22 at 14:41
  • 1
    you are not supposed to use macros for things that can be done in the language. It looks like you are looking for something that cannot be done in C++ – 463035818_is_not_an_ai Jul 26 '22 at 14:44
  • 1
    btw not wanting to modify legacy code doesnt help much to clarify the question when we dont know what legacy code you refer to, or what modifications count as "ok" – 463035818_is_not_an_ai Jul 26 '22 at 14:45
  • "right where it is" ? I thought thats not what you want. Why is then the lambda not ok? – 463035818_is_not_an_ai Jul 26 '22 at 14:50
  • 1
    in your last snippet, the one that you want, the implementation of `g` is inside `f`, does it really matter if its at the end or at the beginning of `f` ? – 463035818_is_not_an_ai Jul 26 '22 at 14:52
  • 1
    you again refer to some existing code that we cannot see. Is that code the first snippet in the question? Then just pass many parameters. Anything else that involves some magic or macros will be more nuisance for future readers. – 463035818_is_not_an_ai Jul 26 '22 at 14:58
  • 2
    @JackBrown: Have you considered that what you're trying to do is not actually "refactoring" the code? That maybe the code is already in its best possible state as is *without* major restructuring? And therefore, if you want to improve it, you have to do some restructuring. Without knowing more about the relationship between the actual `f`s and `g`s, I can't say more, but everything you're talking about leads me to the belief that the code as is is probably decent enough, and all of your proposed changes would make it a worse version of itself. – Nicol Bolas Jul 26 '22 at 15:14
  • 1
    @JackBrown: "*imagine many more local variables in f() that are passed to g().*" That's not why you would change anything about `g` though. If only `f` ever calls `g`, and `f` only calls `g` in one place, then there's no need to change anything. `g` is just a component of `f`. If `f` calls `g` from multiple places, does it do so with different variables? Does `g` take variables by reference and modify them, such that `g` is essentially returning several values? All of these questions are key parts in deciding how to, or *whether to* change anything about this code. – Nicol Bolas Jul 26 '22 at 15:20
  • 1
    @JackBrown: Focusing exclusively on the number of parameters is the wrong way to look at refactoring. – Nicol Bolas Jul 26 '22 at 15:21
  • 1
    @JackBrown: "*I'd just like a lambda-like closure to avoid having to pass so many f()-local variables as parameters to g().*" I know *what* you want. I'm questioning the validity of *why* you want it. I'm saying that it will likely not be a good idea and will likely end up making the code worse. "*my focus is avoiding extensive rearranging of legacy source code.*" The least extensive change is... no change. So why do you want to change it? Why do you feel like the code *needs* to be changed? – Nicol Bolas Jul 26 '22 at 15:24
  • @JackBrown: Then don't change it. You want to change it, but you don't want to change it too much. Why do you want to change it *at all*? And is that a good reason to change it? – Nicol Bolas Jul 26 '22 at 15:25
  • 1
    @JackBrown: So, your *actual* problem is that you have a block of code in `f` which consumes and manipulates some local variables, but you want to extract this code from the scope of `f` into some subsidiary function. But you want to do that without passing those local values as parameters. So basically... you don't want to abide by the rules of structured programming. Like I said in my first comment. Yeah, C++ doesn't have a way to break the foundational programming model upon which it is built. – Nicol Bolas Jul 26 '22 at 15:31
  • @JackBrown: Lambdas don't break structured programming precisely because you *must define them inline*. The lambda gets to access local variables because it is *visually contained* within the scope of those variable definitions. – Nicol Bolas Jul 26 '22 at 15:34
  • 1
    @JackBrown: You're looking too hard at mechanism and not hard enough at the concept of what you want. So long as you want the body of that function to be *outside* of the function whose local variables are being accessed, you are violating the rules of structured programming. That's not a "slightly different context"; that's breaking the rules. – Nicol Bolas Jul 26 '22 at 15:47
  • 1
    @JackBrown: Again, *I understand* what you want. I am explaining 1) why what you want cannot happen, and 2) why what you want is probably a bad idea to begin with. – Nicol Bolas Jul 26 '22 at 15:51
  • 1
    @JackBrown: You said earlier that you're trying to change the legacy code, to extract some `g` from within an `f`. Stop doing that. If using parameters is so onerous to you, then stop messing with the legacy code and leave it alone. – Nicol Bolas Jul 26 '22 at 15:53
  • 1
    @JackBrown: Right; that's why you would have to "extract" it. My point is that if you don't want to use parameters... then you cannot extract `g` from it. You're going to have to leave that block of code there. Those are your two options: build a function and pass parameters, or leave the block of code in `f`. – Nicol Bolas Jul 26 '22 at 15:58
  • @JackBrown: Using lambdas means that the block remains in `f`. You may have named the block, but it's still there in `f`. And even C# local functions still have the block inside `f`, even if its at the bottom of the function definitions. So no, you only have two options: have the block of code in `f`, or use parameters. – Nicol Bolas Jul 26 '22 at 16:05
  • @NicolBolas if C++ had [`partial class`](https://stackoverflow.com/questions/73126392/is-there-a-way-to-emulate-cs-partial-class-in-c) (from C#), `f()` could be rewritten as a functor with `g()` as a member function. – Jack Brown Jul 26 '22 at 16:07

1 Answers1

5

Just put them in a struct

struct S final
{
   struct g_params { //that's it
      double d;
   };

   int i = 123;
   void f()
   {
      g_params d = {1.23};
      g(d);
      // ... assume this is already a large function ...
   }
   // ... lots of code here ...
   void g(g_params& p)
   {
      i = 456; p.d = 4.56;
   }
};
Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • Now I've moved the local variables out of `f()`; again, non-trivial rearranging of legacy source code. – Jack Brown Jul 26 '22 at 15:17
  • Before, the source code looked like `d = 4.56;`. Now it looks like `p.d = 4.56;` That sounds pretty trivial. The lambda is far more rearranging than this is. FYI, under the covers, this is _exactly_ how the lambda works. – Mooing Duck Jul 27 '22 at 15:54