5

I'm trying to create a struct that have 2 function, which may be rewritten if needed later. The functions are: onClicked() and onClickedRight(). The code for the struct:

typedef struct {
    QString text;
    QString infoText;
    QUrl iconSrc;
    QColor iconColor;
    void (*onClicked)() = nullptr;
    void (*(*onClickedRight))() = &onClicked; // by default, execute the same function from onClicked()
} ConfigButton;

How I'm trying to execute these functions:

ConfigButton b;
...
// test if click funtion has been defined, to execute it
if (b.onClicked)
    b.onClicked(); // this one work just fine

...

if (*(b.onClickedRight))
    (*(b.onClickedRight))(); // this one crashed

Is it even possible? Am I missing something?

9
  • 2
    Does the value in b get copied from somewhere else? If so then onClickedRight can point to that source structure. Commented Jan 29, 2020 at 19:14
  • 7
    C or C++? Please decide. Commented Jan 29, 2020 at 19:18
  • 1
    BTW, in C++ you don't need the typedef in typedef struct. Commented Jan 29, 2020 at 19:24
  • 1
    Assuming C++ this should work. Please reduce your problem to a minimal reproducible example that others can run that will reproduce the problem. Commented Jan 29, 2020 at 20:08
  • void (*onClicked)() = nullptr; inside the typedef wrong syntax in c Commented Jan 29, 2020 at 20:37

5 Answers 5

2

When onClicked is a function, both &onClicked and onClicked evaluate to the same thing -- a pointer to the function.

If you want to create a pointer to a function pointer, you need a pointer to a function as a variable first.

However, given your usage, you need just a pointer to a function.

typedef struct {
    QString text;
    QString infoText;
    QUrl iconSrc;
    QColor iconColor;
    void (*onClicked)() = nullptr;
    void (*onClickedRight)() = onClicked;
} ConfigButton;

and

if ( b.onClickedRight)
    b.onClickedRight();
Sign up to request clarification or add additional context in comments.

4 Comments

I think the point is that onClickedRight by default reuses value of onClicked even if onClicked was assigned. In your example user will have to assign both onClicked and onClickedRight.
@R2RT, I trust the OP to take care of such business logic. I pointed out the error in capturing the pointer to the function.
Thank you for your answer @RSahu. My problem was that I copied the struct, so my pointer was pointing to the wrong address (the address from the original struct, as pointed in jxh answer). As pointed by R2RT, I wanted that onClickedRight by default reuses value of onClicked, that's why I used a pointer to a pointer.
@RafaelVissotto-Inel, glad you were able to see the source of the problem. Happy programming.
2

I think you can still solve your issue with a pointer to a function pointer, yet it is a bit clumsy, since you have to call this pointer-pointer in a different manner than you do with a "normal" function pointer. The call would look like (*(aButton.onRightClick))(), and you need to let onRightClick point to a pointer object pointing to a function rather than assigning the function directly.

I suppose you are looking for a way to define that onRightClick shall by default "inherit" the behaviour of onClick, unless the user overrides this and assigns a different behaviour to onRightClick. I see two requirements that a possible solution shall fulfill:

1) If onRightClick did not get overridden, it shall inherit every change made to onClick

2) If onRightClick gets overridden, it gets decoupled from onClick.

You can solve this with "simple" function pointers by assigning onRightClick a function that simply calls the one assigned to onClick. The following code shows this for C++; the approach can be transferred to C (though you then need to pass the "this" to the function calling onClick:

void defaultOnClick() {
    std::cout << "defaultOnClick" << std::endl;
}

void otherClick() {
    std::cout << "otherClick" << std::endl;
}

void rightClick() {
    std::cout << "rightClick" << std::endl;
}


typedef std::function<void(void)> onClickFunctionType;

struct ConfigButton {
    onClickFunctionType onClick = defaultOnClick;
    onClickFunctionType onRightClick = [this](){ this->onClick(); };
} ;

int main() {

    ConfigButton configButton;

    std::cout << "default for both onClick and onRightClick (calling onClick)" << std::endl;
    configButton.onClick();
    configButton.onRightClick();

    std::cout << "special click for onClick; 'inherited' by onRightClick" << std::endl;
    configButton.onClick = otherClick;
    configButton.onClick();
    configButton.onRightClick();

    std::cout << "special click for onClick; different one for onRightClick" << std::endl;
    configButton.onRightClick = rightClick;
    configButton.onClick();
    configButton.onRightClick();

}

Output:

default for both onClick and onRightClick (calling onClick)
defaultOnClick
defaultOnClick
special click for onClick; 'inherited' by onRightClick
otherClick
otherClick
special click for onClick; different one for onRightClick
otherClick
rightClick

1 Comment

That's exactly what I mean. The problem was that I was copying the struct, so the pointer was pointing to the wrong address. Thank you for your answer, you gave me a nice approuch to how to solve the problem.
1

Answer adjusted, since OP has removed from the tag list.

The code works as is. So you are doing something else wrong.

However, using a pointer to a pointer to a function in this way may not have the semantics that you want. If the structure gets copied to another structure, the onClickedRight member in the copy is not pointing to the onClicked pointer in its own instance. It is instead pointing to the onClicked pointer of the original instance.

a.onClickedRight = &a.onClicked;
b = a;
assert(b.onClickedRight == &a.onClicked); // Is this intentional?

What this means is that you have to be extra careful about how you use structures that contain pointers to its own members (and pointers to anything, really). You will probably need some kind of deep copy method (so, according to TRoT, you need a copy constructor, an assignment operator, and a destructor).

In any case, the C++ code is not really idiomatic. For myself, I would probably leverage virtual methods. The virtual method syntax can easily accommodate this use case.

struct ConfigButton {
    QString text;
    QString infoText;
    QUrl iconSrc;
    QColor iconColor;
    virtual void onClicked() const = 0;
    virtual void onClickedRight () const { onClicked(); }
};

struct Foo : ConfigButton {
    void onClicked () const {
        //...
    }
};

If you follow this method, this will also work.

3 Comments

My problem was exactly because I was copying the struct. You clarified perfectly my problem. Thank you!
The original code allows for the handler to be set at runtime whereas the virtual function option doesn't
@M.M: That use case would get handled differently than in the original code. You would create conversion methods to achieve it.
1

One possible approach is to have functions that implement the logic of firing the handler. You already have some logic (if (onClicked)) that the caller has to do anyway, so this minimizes the possibility of the caller making a mistake .

struct ConfigButton {
    // ...

    void Fire_OnClicked()
    {
         if ( onClicked )
             onClicked();
    }
    void Fire_OnClickedRight()
    {
         if ( onClickedRight )
             onClickedRight();
         else
             Fire_OnClicked();
    }

private: 
    void (*onClicked)() = nullptr;
    void (*onClickedRight)() = nullptr;
};

You could combine this with the std::function version, testing for empty instead of requiring "empty" to be represented by a lambda performing the default action. And if there are multiple handlers you want to have default fallback you could reduce boilerplate by making a template Fire function.


Another approach that might work would be to make a custom handler type with similar semantics to std::function but its () operator will perform a default action if no function has been set .

Comments

0

In the C language functions pointers are the only place where hiding pointers behind typedefs makes sense

https://godbolt.org/z/Gb_WEy

#include <stdio.h>

typedef int (*fptr_t)();

typedef struct
{
    fptr_t fptr;
    fptr_t *pfptr;
    fptr_t **ppfptr;
    fptr_t ***pppfptr;
}MYSTRUCT_t;

int foo(char *caller)
{
    printf("Function name = %s, caller = %s\n", __FUNCTION__, caller);
    return 0;
}

int main()
{
    MYSTRUCT_t mystr;

    mystr.fptr = foo;
    mystr.pfptr = &mystr.fptr;
    mystr.ppfptr = &mystr.pfptr;
    mystr.pppfptr = &mystr.ppfptr;

    printf("mystr.fptr=%p mystr.pfptr=%p func=%p\n", (void *)mystr.fptr, (void *)mystr.pfptr, (void *)&foo);

    foo("foo");
    mystr.fptr("mystr.fptr");
    (*mystr.pfptr)("mystr.pfptr");
    (*(*mystr.ppfptr))("mystr.ppfptr");
    (*(*(*mystr.pppfptr)))("mystr.pppfptr");
}

2 Comments

IMO it is clearer to not use pointer typedefs. You can typedef the function type
@M.M it is probably personal preference\

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.