1

Ok, maybe my problem is a little contrieved.

I am trying to find an elegant solution in order to reduce the following boilerplate code each time I want to modify a part of an object which is only accessible through a const Getter and a non const Setter.

Content c = container.GetContent();
c.SetX(3); 
container.SetContent(c);

I know I could have a non const getter, but I'd like to stick with it for the time being.

So, I tried to use lambdas and I have currently the following implementation :

#include <iostream>

class Content
{
public:
    Content(int x) :mX(x) {}
    const int GetX() const
    {
        return mX;
    }
    void SetX(const int &x) 
    { 
        mX = x;
    }
private:
    int mX;
};


//for clarity ContentFunctionChanger is a typedef for any function of type : void f(Content &)
typedef void (*ContentFunctionChanger)(Content &);


class Container
{
public:
    Container(const Content &c) :mContent(c) {}
    const Content & GetContent() const
    {
        return mContent;
    }
    void SetContent(const Content &c) 
    { 
        mContent = c;
    }

    void ChangeContent(ContentFunctionChanger &function)
    {
        (*function)(mContent);
    }

private:
    Content mContent;
};


int main()
{
    Content content(1);
    Container container(content);

    std::cout << "x=" << container.GetContent().GetX() << std::endl;

    {
        //Classic method using Get() then Set()
        Content c = container.GetContent();
        c.SetX(3);
        container.SetContent(c);
        std::cout << "x=" << container.GetContent().GetX() << std::endl;
    }

    {
        //Method 1 : with a named lambda function whose type is written at the declaration
        //It works, but it is not concise
        ContentFunctionChanger func = [] (Content & c) { c.SetX(5); };
        container.ChangeContent(func);
        std::cout << "x=" << container.GetContent().GetX() << std::endl;
    }
    /*
    {
        //Method 2 : with a named lambda function whose type is not written (using auto)
        //It will not compile...
        auto func = [] (Content & c) { c.SetX(7); };
        container.ChangeContent(func);
        std::cout << "x=" << container.GetContent().GetX() << std::endl;
    }

    {
        //Method 3: with an anonmymous lambda. 
        //Concise enough, but it does not compile either...
        container.ChangeContent([] (Content & c) { c.SetX(9); } );
        std::cout << "x=" << container.GetContent().GetX() << std::endl;
    }
    */

    return 0;
}

My problem is that Methods 2 and 3 are more concise, but they will not compile. I wonder if there is hope to make them compile.

Can anyone help ?

0

4 Answers 4

3

Your problem is that you try to pass a temporary object as a reference: remove the & from this declaration and it will work:

void ChangeContent(ContentFunctionChanger &function)

(well, you'll also need to rename func2 to func in one place). It actually doesn't really make any sense to pass a function pointer by reference anyway. Doing so just adds another indirection without any benefit and unnecessary indirections tend to just cost time.

Just to explain where the temporary is coming from: the type of the lambda expression is a unique type for each lambda expression. If the lambda function has an empty capture it can be converted to a function pointer. In your first code you did this conversion explicitly, yielding an lvalue which could be bound to the reference. In the other two cases you relied on an implicit conversion which yields and rvalue which can't be bound to a non-const reference (that is, you could have fixed the problem by adding const in front of the & but the extra indirection is still pointless).

Sign up to request clarification or add additional context in comments.

Comments

2

You can use templates to enable method 3:

template<typename F>
void ChangeContent(F function)
{
    function(mContent);
}

That will allow you to pass anything callable (functors, for example).

Another (C++03) approach would be to implement fluent interface for Set method:

// kind of a functional set — if we want Set to constant, we need to return a new object
Content SetX(const int &x) const
{
    Content ret = *this;
    ret.mX = x;
    return ret;
}

And use it as follows:

{
    //Fluent interface
    container.SetContent( container.GetContent().SetX(111) );
    std::cout << "x=" << container.GetContent().GetX() << std::endl;
}

2 Comments

@woolstar local object are never returned by reference. In this case this is const, so we can't modify it to return *this.
I like your solution with the template, as I can avoid scratching my head with the convoluted syntax of the function pointers.
1

Going in an entirely different direction, have SetX return a reference to the object, and then you can chain your arguments:

Content & SetX(const int &x) 
    { 
        mX = x;
        return * this ;
    }

...

container.SetContent( container.GetContent().setX( 3)) ;

However in this particular case, container.GetContent() returns a const so you can't even call setX on it, which begs the question of why bother calling GetContent() at all if you have to create a new object to modify it.

While others have modified the behavior of setX to return a new object, I think that doesn't fit the verb. I would expect set* to modify an object, not return a new one. So here is how I tackle the problem of preserving the meaning of setX and deal with a const value from a getter.

In the simple case where you are not copying things from Content, just make a new one:

container.SetContent( Content( 3)) ;

Or in the more complicated where there is some worthwhile state, throw in a temporary object:

container.SetContent( Content( container.getContent()).setX( 3) ) ;

Thankfully, I think the trend for getters/setters is in decline.

3 Comments

Not a bad idea also !
...But it does not compile (as woolstar explained in his comment). But I do like the original concept of chaining
Addressed the const of getContent(). I don't like setX returning a new object.
0

Below is the corrected code using Barmaley.exe suggestion.

We are getting closer to having a behaviour that resemble C# properties, since a call like

container.ChangeContent([] (Content & c) { c.SetX(9); } );

can call a non trivial setter (that will for example change the object modification date)

Full code below :

#include <iostream>

class Content
{
public:
    Content(int x) :mX(x) {}
    const int GetX() const
    {
        return mX;
    }
    void SetX(const int &x) 
    { 
        mX = x;
    }
private:
    int mX;
};

class Container
{
public:
    Container(const Content &c) :mContent(c), mWasUpdated(false) {}
    const Content & GetContent() const
    {
        return mContent;
    }
    void SetContent(const Content &c) 
    { 
        mContent = c;
        mWasUpdated = true; //dummy example of a non trivial setter
    }
    bool WasUpdated() const
    {
        return mWasUpdated;
    }

    //FnContentChanger can be a function, a functor, any callable that modifies Content...
    //We are getting closer to having a behaviour that resemble C# properties 
    template<typename FnContentChanger>
    void ChangeContent(FnContentChanger function)
    {
        Content c = GetContent();
        function(c);
        SetContent(c);
    }

private:
    bool mWasUpdated;
    Content mContent;
};


int main()
{
    {
      //Classic method using Get() then Set()
      Content content(1);
      Container container(content);

      std::cout << "x=" << container.GetContent().GetX() << " wasUpdated=" << container.WasUpdated() << std::endl;
      Content c = container.GetContent();
      c.SetX(3);
      container.SetContent(c);
      std::cout << "x=" << container.GetContent().GetX() << " wasUpdated=" << container.WasUpdated() << std::endl;
    }

    {
      //Method 2: with an anonmymous lambda. 
      Content content(1);
      Container container(content);

      std::cout << "x=" << container.GetContent().GetX() << " wasUpdated=" << container.WasUpdated() << std::endl;
      container.ChangeContent([] (Content & c) { c.SetX(9); } );
      std::cout << "x=" << container.GetContent().GetX() << " wasUpdated=" << container.WasUpdated() << std::endl;    
    }
    return 0;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.