0

Consider the following code base design pattern:

template<typename T>
class Foo {
public:
    Foo(T t) : class_member_{t} {}
    const T class_member() const { return class_member_;}
    
    Foo(const Foo& other ) {
        class_member_ = other.class_member_;
    }

    Foo& operator=(const Foo& other) {
        class_member = other.class_member_;
        return *this;
    }

    template<template<class> class U, typename V>
    friend auto extend(U<V>& u, V v);

    void update_from_hidden() {
        class_member_ = hidden_member_;
    }
protected:
    T hidden_member_{class_member_ + 1};
    

private:
    T class_member_;
    void update_class_member(T new_val) { class_member_ = new_val;}    
};

template<template<class> class T, typename U>
auto extend(T<U>* tt, U val) {
    auto impl = [] (T<U>* t, U val) {
        t->hidden_member_ += 5; // okay I want this lambda to be able to
                              // modify Foo::hidden_member
        t->class_member_ = 7; // Invalid, I don't want this lambda to have
                              // access to Foo's private members
        return *t;
    };
}

int main() {
    Foo<int> foo(3);
    foo = extend(&foo, 2);
    return foo.class_member();
}
    

Now, these two lines within the body of the lambda and this line from main:

t->hidden_member_ += 5;
t->hidden_member_ = 7;
// main 
foo = extend(&foo, 2);
   

Will not compile, because these members are protected and private within this context, and operator=() can't resolve the value 2.

While trying to keep this code structure, I want lambdas of these type that are through friend functions of some arbitrary class object to be able to have direct access to the class's protected members while still restricting them from the class's private members both variables and private functions.

Can this be achieved, and if so, what would I minimally have to do to this class and or lambda to achieve this behavior?

If not, is there another existing code pattern that would fit this model?

I want to abstract specific methods to outside of the classes, but I prefer to use lambda's within this context. In fact I would have preferred to have something like this instead, but this isn't valid C++ code that I know of...

class Bar {
public:
      Bar(int val) : member_{val} {
protected:
      int hidden_;
private:
      int member_;
};

auto extend = [](Bar* bb, int val) {
    friend class Bar; // not valid or legal as far as I know
    bb->hidden_ += 3; // I want this to be valid
    bb->member_ = 5; // I want this to be blocked, not valid
    
    return *bb;
};

This is where I hit my road block... It's not the implementation of the methods that I'm concerned about, it's how class Foo and lambda extend integrate with each other through friend or some other mechanism within the manner and context that I described above... not sure what to do here.


-Edit-

Okay, here is a use case scenario.

Let's say I have a WaterPipe class that has private members that can only be changed within the class itself. It has some update method to change that private member internally. Now, it has protected members that are not accessible to outside of the class, but want to expose them to lambda's that will modify this class's dimensions or state, but not it's internal properties or behavior.

class WaterPipe {
public:
    WaterPipe(float length, float diameter) 
    : length_{length}, diameter_{diameter}, has_changed{false} 
    {
        calculate_props();
    } 

    friend extend(WaterPipe* pipe, float newLength, float newDiameter);

    const auto length() const { return length_; }
    const auto diameter() const { return diameter_; }
    const auto volume() const { return volume_flow_; }
    const auto direction() const { return direction_flow_; }
    const auto pressure() const { return pressure_flow_; }

protected:
    float length_;
    float diameter_;
    void recalculate() { calculate_pops(); }
 private:
    float volume_flow_;
    float direction_flow_;
    float pressure_flow_;

    void calculate_props() {
        // implementation here
    }
};

auto extend_water_pipe(WaterPipe* pPipe, float newLength, float newDiameter) {
    bool changed = false;
    auto impl = [](WaterPipe* pipe, float newLength, float newDiameter)
        if (pipe->length_ != newLength || pipe->length_ != newDiameter) {               
            pipe->length_ = newLength;
            pipe->diameter_ = newDiameter;
            pipe->recalculate();
             changed = true;
        }
        return pipe;
    };

    if ( !changed ) { return pPipe; }

    return impl(pPipe, newLength, newDiamter)();  
}

In some GUI Application:

void object_construction_in_app(...) {
    // user grabs water pipe drawing tool
    // user clicks point on graph node and holds mouse down
    // drags it in some direction, stops and releases the mouse
    // this calls the constructor on the pipe giving it the length
    // the diameter was already set via the `water pipe drawing tool`
    
    // create a new pipe object here with specified values
    // and store it into some container to be rendered.        
}

// some frames later
void object_update(...) {
    // user selects already constructed pipe
    // and decides to either change it's diameter through context menu
    // or changes its length by clicking on one of its ends and starts
    // to drag it again... 

    // get object from container and use `friend function - lambda`
    // to update it's dimensions, return back that object to have
    // it be rendered, store it into some other container, such as
    // a queue, etc... 
}

The Water Pipe will calculate it's own internal values... The external friend lambda, will access it's protected members only by resetting, the length or diameter and invoking the protected recalculate() function which then invokes the class's internal private calculate_props() function. The lambda then returns either a pointer to the class object, or returns a deference pointer to the class to allow a copy or assignment to be done.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • 3
    What you're describing is more of an anti-pattern. You're attempting to open up your classes to expose some private data and hide other data and do that via the use of `friend`, which really is an abuse of friendship. The best advice will come if you provide a real example of why you need this, instead of abstracting your requirements into generic terminology. There are various approaches to this type of problem, but without knowing your primary motivations for this kind of design, it's hard to suggest appropriate alternatives. – paddy Feb 15 '21 at 01:03
  • @paddy No, I want these lambda's to work specifically with these classes, and only these classes but have only access to their `protected members` both variables and functions, but want to restrict them completely from all of the class's `private members`. – Francis Cugler Feb 15 '21 at 01:06
  • 1
    `class BarEx : public Bar { public: void extend(int val) { hidden_ += val; } };` – RbMm Feb 15 '21 at 01:31
  • @RbMm I know I can use functions through inheritance... but I don't want to do that. I want the lambda's to act as free standing functions to operate on the class to change its dimensions or state of the object, but not to change its internal behaviors or properties... Refer to my example that I added as a edit to the original question... I could have made `length` and `diameter` public, but I don't want them to be access from other outside sources, only from within the class itself and lambdas that are friends to this class... Also, I want to restrict the lambda's access to protected only. – Francis Cugler Feb 15 '21 at 01:44
  • @paddy I added a new section and in the edit section, I show a sample case of how it may be used and why I would want to use it that way... – Francis Cugler Feb 15 '21 at 01:45
  • 3
    No, you cannot suddenly gain ability to modify members - that's the whole purpose of public/private/protected/friend. What's even the difference between a lambda modifying the private/protected variable or doing so anywhere else in the code? Just make the variable public or modify it via the class's member functions - you could modify them from static member functions as well. – ALX23z Feb 15 '21 at 02:03
  • 1
    you want that some function - `extend_water_pipe` in concrete case have access to protected members of class ( `WaterPipe` ) but have not access private. this possible (and easy) if you declare class inherited from `WaterPipe` and this function will be member . but why not simply have `WaterPipe::extend_water_pipe(float newLength, float newDiameter);` ?! and also amazes me - for what you use lambda inside `extend_water_pipe` ? – RbMm Feb 15 '21 at 02:09
  • The line `if ( changed ) { return pPipe; }` in your code is useless as `changed`is allways false at that point. I think you are trying to make things more complicated that they need to be, Use regular class instead of a lambda. – Phil1970 Feb 15 '21 at 02:20
  • 2
    I share the same sentiments. Using lambdas and friendship instead of providing a simple member is a very roundabout way of approaching this. It adds complexity and code bloat for no obvious reason. The underlying problem remains one of selective hiding. Maybe instead you want to use a Mixin Interface pattern, and/or encapsulate all the user interface steps in another class that manages the discrete operations and make _that_ class a friend of `WaterPipe`. You can query an instance of that "Builder"-type object via a public method. – paddy Feb 15 '21 at 02:21
  • Remember that any time you make something a `friend`, you are inherently creating a tight coupling of that friend's implementation with the implementation of your class. Recall the mantra _"only your friends can touch your privates"_. If that level of exposure is a concern then you have a design issue and you should not be using `friend` at all. – paddy Feb 15 '21 at 02:25
  • @paddy, I understand that... but from what I described... is there another way to do something similar with outside lambda's? ... make all of the members private, and the lambda's access them through members... however, I want only these lambda's or free functions to have access to them and no other outside class to be able to... I don't want these type of `invoking to update with new values methods` to be a part of the class. They're not necessarily performing the calculations but feeding the new values to the class and invoking the class to update itself. – Francis Cugler Feb 15 '21 at 02:32
  • @Phil1970 Not really, it was Pseudo code that I typed on the fly... there were two over looked issues that if I had them in my IDE, I would of found and fixed them very quickly... First setting the update should be done after the class's members have been changed and internal members invoked. Second, in the `if-statement` outside of the `lambda` I forgot to add a `!` operator before `changed` and there was a typo on the end of the variable's name. I fixed those now! – Francis Cugler Feb 15 '21 at 02:37
  • @ALX23z I want specific members to act as if they were public but only to a subset of functions... and not the rest of the world. I don't want these functions to be a part of the class itself. I want to these functions or lambdas to be able to have access to them. I know what `public`, `protected`, and `private` mean within C++. `public` is visible to everyone. `protected` is visible to derived classes and friends, `private` is invisible to everyone but `friends`... – Francis Cugler Feb 15 '21 at 02:41
  • @ALX23z ... continued. This is where I think they kind of got this wrong in C++. It should be that anything declared `private` is always private to everyone except for itself. Then, `protected` should allow, bases, derived, and friends to have access to those fields. This would give a class, 3 different levels of security access. Open, Specialized to inheritance, nested classes or structs, and friends, and OFF LIMITS to everyone except ME! – Francis Cugler Feb 15 '21 at 02:44
  • @ALX23z Now, I know they won't change this because of backwards compatibility and long standing existing code bases for they don't want to "break" existing code, which is completely understandable... but I think they got the `protected` wrong along with allowing access to `private` via `friend`, friend should be `restricted` to `protected` only... This is my opinion though... – Francis Cugler Feb 15 '21 at 02:47
  • @paddy, Now I do like your comment on the "Builder"-type object. I would need to see the `design pattern` in use or an example to implement its integration properly... – Francis Cugler Feb 15 '21 at 02:51
  • 2
    You should also consider an Interface pattern. It seems to me that what you're describing is really the desire to allow reasonably free-reign on your objects for anything designed to manipulate them, but only very restricted access to anyone else. You can do this by putting all your public crap in the interface, and designing the rest of your system so that the "public" users only ever get to see the pure interface. You can hide all the other implementation details this way. I don't think your opinion on how `friend` should behave would be shared by anybody experienced in C++. – paddy Feb 15 '21 at 02:57
  • @paddy I know, I was being rhetorical, satirical in that response... It seems it should be that way... however, working with C++ on & off for the past 20 years... you become accustomed to it and prefer not to see certain changes... If I had access to C++20... I'd just use `concepts`, `modules`, and other facilities... but I'm currently stuck with C++17 atm... – Francis Cugler Feb 15 '21 at 03:05
  • @FrancisCugler but that's the point that this set of functions that have the right of usage of private/protected members must be declared within the class as otherwise how do you differentiate it from free global usage? Thus you go back to methods declared within class. Ofcource, you can just add free functions that calls these class methods if you want a free function. – ALX23z Feb 15 '21 at 05:22
  • While one one hand, you want to be too much restrictive by giving friendship only to some functions/lambdas, **on the other hand, you don't even follow recognized rule that all data members should be private!** `length_` and `diameter_` should be private! – Phil1970 Feb 16 '21 at 00:03
  • 1
    By the way `extend_water_pipe` function **has not been fixed**. As the lambda is **called after** the following line `if ( !changed ) { return pPipe; }` you will **always have the value set** by the line `bool changed = false;` **at the start**. Also, the lambda does not capture anything so the code **does not even compile**. – Phil1970 Feb 16 '21 at 00:32
  • @Phil1970 It was Pseudo code to illustrate what I was trying to achieve, it wasn't meant to be compiled. But, yes, you are right. The lambda would need a capture clause for the bool flag, and the arguments that are passed into the outer function body as its parameters. Last night I worked on a hierarchical structure related to this same application design that I presented here, with that in mind and the way it turned out. I may go with that instead. I may supply its implementation here as an answer, then follow up with a new question based on that, which may be posted here or on Code Review. – Francis Cugler Feb 16 '21 at 00:51

0 Answers0