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?

  • 2
    Does the value in `b` get copied from somewhere else? If so then `onClickedRight` can point to that source structure. – 1201ProgramAlarm Jan 29 '20 at 19:14
  • 7
    C or C++? Please decide. – Stephan Lechner Jan 29 '20 at 19:18
  • 1
    BTW, in C++ you don't need the `typedef` in `typedef struct`. – Thomas Matthews Jan 29 '20 at 19:24
  • 1
    Assuming C++ this should work. Please reduce your problem to a [mcve] that others can run that will reproduce the problem. – dbush Jan 29 '20 at 20:08
  • `void (*onClicked)() = nullptr;` inside the typedef wrong syntax in c – 0___________ Jan 29 '20 at 20:37
  • Works for me: https://tio.run/##bZDNbsIwEITvfoqRkEqC2tIzoT2UJ6C0t0pRcOywqmNHZl2BEM@eOiH8SfXJ@nY8M2vZNE@VlO2IrDShVJiT27JXRf0mBO8bVSoNuSk8JssVe7JVdsXObvk8/PLmOiHLWC6ccT4TowjIKthgTMMeL@1ZFXOCZBwE4hncwWrH2R0hq93nlcYgUIxeeTmQPqhnQ2RHfx2VSCaRGZI/qkyTFK/nEneSG9EHVRtOT9KHC80wnWK9R@xcBMOPUDslAyvwRmFb1Ao6WMnkLLR3NS4Pk1QcsXBWU/UemJ3N2j5TO4eYcYg/UM5m0gXGfI4872zyvLuPv@04w1GI7ifrgmyv71vf@mF92mT9fMmMzaP9CZPu1rsZDvv9T5M0E8f2Dw – jxh Jan 29 '20 at 20:47
  • @jxh for sure :D especially `std::cout << __func__ << '\n'`. It is one of the basic examples of the C code. and https://i.stack.imgur.com/TGwf5.png – 0___________ Jan 29 '20 at 21:42
  • @P__J__: The question is tagged with both C++ and C, but the code offered by the OP will obviously not compile in C. I don't really understand your point. The OP says the code crashes, and I illustrated it does not. In C, it just fails to compile. – jxh Jan 29 '20 at 22:15
  • Indeed, It was my mistake to tag it with C, I'm sorry. My code crashed because I was making a copy of my struct. – Rafael Vissotto - Inel Jan 30 '20 at 20:50

5 Answers5

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();
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • 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 Jan 29 '20 at 20:16
  • @R2RT, I trust the OP to take care of such business logic. I pointed out the error in capturing the pointer to the function. – R Sahu Jan 29 '20 at 20:20
  • 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. – Rafael Vissotto - Inel Jan 30 '20 at 20:44
  • @RafaelVissotto-Inel, glad you were able to see the source of the problem. Happy programming. – R Sahu Jan 30 '20 at 20:54
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
Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
  • 1
    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. – Rafael Vissotto - Inel Jan 30 '20 at 20:35
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.

jxh
  • 69,070
  • 8
  • 110
  • 193
  • My problem was exactly because I was copying the struct. You clarified perfectly my problem. Thank you! – Rafael Vissotto - Inel Jan 30 '20 at 20:32
  • 1
    The original code allows for the handler to be set at runtime whereas the virtual function option doesn't – M.M Jan 30 '20 at 22:30
  • 1
    @M.M: That use case would get handled differently than in the original code. You would create conversion methods to achieve it. – jxh Jan 31 '20 at 00:22
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 .

M.M
  • 138,810
  • 21
  • 208
  • 365
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");
}
0___________
  • 60,014
  • 4
  • 34
  • 74