2

The context of this question revolves around hard typed genetic programming.

I would like to return a function pointer from a function but these functions pointers point to functions with different return types. In another stack overflow question (Function Pointers with Different Return Types C) a return type of union was suggested however I am struggling with the implementation.

I am fairly new to C++ so please forgive my ignorance if it shows.

#include <iostream>
#include <string>

float Add(float a, float b) { return a + b; }
bool IfElse(bool a) { if (a) { return true; } else { return false; }; }

union return_type
{
    float(*ffptr)(float, float);
    bool(*bfptr)(bool);
};  

union fptr(std::string OPERATION) {
    if (OPERATION == "Add") {
        return_type.ffptr = Add;
    } else if (OPERATION == "IfElse") {
        return_type.bfptr = IfElse;
    }

    return return_type;
}

int main() {
    std::cout << fptr("Add") << std::endl
    return 0;
}

I expect (or rather would like) this to print the address of the function Add

timrau
  • 22,578
  • 4
  • 51
  • 64
Ivor Denham-Dyson
  • 655
  • 1
  • 5
  • 24

3 Answers3

4

TL;DR version: I think you may be trying to hammer a solution into fitting a problem. Consider using something like the Visitor pattern to decouple the problem so that you don't need to know the type of the data.

A union isn't a type. It's a type of types, like a class or a struct. In order to use a return_type, you have to make an object that is a return_type. That means

union fptr(std::string OPERATION) {
    if (OPERATION == "Add") {
        return_type.ffptr = Add;
    } else if (OPERATION == "IfElse") {
        return_type.bfptr = IfElse;
    }

    return return_type;
}

needs to look more like

return_type fptr(std::string OPERATION) {
    return_type result; // make a return_type object
    if (OPERATION == "Add") {
        result.ffptr = Add; // set a member of the object
    } else if (OPERATION == "IfElse") {
        result.bfptr = IfElse;
    }

    return result; // return the object
}

Then you can

int main() {
    std::cout << fptr("Add").ffptr(10,20) << std::endl; // call the stored function
    return 0;
}

The big problem with unions is knowing what is in them. You can only safely use ffptr if ffptr was the last member set.

int main() {
    std::cout << fptr("Add").bfptr(true) << std::endl;
    return 0;
}

will compile, but will not behave well at all when run. What will happen is undefined, but odds are good that it won't be pretty.

You have to be absolutely certain that the function stored in the union is the correct one. If your compiler is up to date, you can use std::variant to help out here. It will at least tell you you're headed in the wrong direction by throwing an exception

#include <iostream>
#include <string>
#include <variant>

float Add(float a, float b) { return a + b; }
bool IfElse(bool a) { if (a) { return true; } else { return false; }; }

using return_type = std::variant<float (*)(float a, float b), bool (*)(bool a)>;

return_type fptr(std::string OPERATION) {
    return_type result;
    if (OPERATION == "Add") {
        result = Add;
    } else if (OPERATION == "IfElse") {
        result = IfElse;
    }

    return result;
}

int main() {
    std::cout << std::get<float (*)(float a, float b)>(fptr("Add"))(10,20) << std::endl;
    try
    {
        std::cout << std::get<bool (*)(bool a)>(fptr("Add"))(true) << std::endl;
    }
    catch (const std::bad_variant_access& e)
    {
        std::cout << e.what() << std::endl;
    }
        return 0;
}

But at the end of the day it's still not all that useful. I think you may find the Visitor pattern or one of its friends more helpful.

user4581301
  • 33,082
  • 7
  • 33
  • 54
  • You have anticipated my next problem. Much appreciated. – Ivor Denham-Dyson Jun 14 '19 at 23:24
  • 3
    @JakeDyson I'm sorry I don't have a better solution for it than do something different. If not a Visitor, perhaps if you returned a [function object](https://isocpp.org/wiki/faq/pointers-to-members#functionoids-teaser) that derives from a common type (so they all look the same) and knows how to get the values it needs from the nodes in your tree as well as performing a computation. – user4581301 Jun 14 '19 at 23:37
2

You were close. Be careful not to conflate the declaration of the (union) type name and the function return value. Since you want to reference pointer address, I added a void* to your union (fpaddr), so you can clearly identify that you are printing an address. Note that your fptr("Add") returned the union, and you needed to disambiguate which interpretation of the union you wanted.

#include <iostream>
#include <string>

float Add(float a, float b) { return a + b; }
bool IfElse(bool a) { if (a) { return true; } else { return false; }; }

//typedef //in C you would use 'typedef'
union fp_return_t
{
    float(* ffptr )(float, float);
    bool(* bfptr )(bool);
    void* fpaddr;
}; //fp_return_t; //in C you would give the name here

fp_return_t fptr(std::string OPERATION) {
    fp_return_t fp_return;
    if (OPERATION == "Add") {
        std::cout << "Add:" << (void*) Add << std::endl;
        fp_return.ffptr = Add;
    } else if (OPERATION == "IfElse") {
        std::cout << "IfElse:" << (void*) IfElse << std::endl;
        fp_return.bfptr = IfElse;
    }

    return fp_return;
}

int main() {
    std::cout << fptr("Add").fpaddr << std::endl;
    return 0;
}
ChuckCottrill
  • 4,360
  • 2
  • 24
  • 42
  • 1
    Thank you, unfortunately your response came a little too late to be identified as the answer but nonetheless you picked up the error. I'm confused about the purpose of void in both answers, mind elaborating? If not perhaps another SO post for another day :) – Ivor Denham-Dyson Jun 14 '19 at 22:56
  • 1
    operator << doesn't know that you only want the address of the function. The `void *` gives it a hint by turning the function pointer into an anonymous pointer that `<<` does no how to display. Eliminates a page or so of compiler diagnostics. – user4581301 Jun 14 '19 at 23:00
  • The chosen answer is more 'accurate', but I added the `void* fpaddr` to use the union to perform the cast, mostly because it would be clear to a wider audience, and to illustrate that you should qualify which union member/type to use. – ChuckCottrill Jun 14 '19 at 23:11
  • I'm wrong about the volume of compiler diagnostics. g++ spits out one line and it's almost readable. *warning: the address of 'float Add(float, float)' will never be NULL* probably won't make much sense, but at least it's not an intimidating wall of text. – user4581301 Jun 14 '19 at 23:15
  • All in all, does this mean I would have to know the member type in order to use it? – Ivor Denham-Dyson Jun 14 '19 at 23:36
  • I believe that is the case. https://stackoverflow.com/questions/16623226/how-to-check-what-type-is-currently-used-in-union – Ivor Denham-Dyson Jun 14 '19 at 23:39
  • 1
    Yes, you need to know the member type. Which is why you typically see these unions are part of a struct (at least in C). So, you may see this problem tackled in C++ using class inheritance, where each function class would be derived from a (probably abstract) base class, and C++ determines which derived class (and thus function) was assigned. – ChuckCottrill Jun 14 '19 at 23:59
1

I'm not quite sure what the ultimate goal is but here's how you can make the above compile and print the function address (printing of a plain Add added for comparison):

#include <iostream>
#include <string>

float Add(float a, float b) { return a + b; }
bool IfElse(bool a) { return a; }

union return_type
    {
    float(*ffptr)(float, float);
    bool(*bfptr)(bool);
    };  

union return_type fptr(std::string OPERATION) {
    union return_type r;
    if (OPERATION == "Add") {
        r.ffptr = Add;
    } else if (OPERATION == "IfElse") {
        r.bfptr = IfElse;
    }

    return r;
}

int main()
{
    /*the void*-cast is technically nonportable but it's hard to
      print fn-pointers portably*/
    std::cout << reinterpret_cast<void*>(Add) << '\n';

    /*you should know which union member is active: */
    std::cout << reinterpret_cast<void*>(fptr("Add").ffptr) << '\n';
    /*^should be the same address*/
    return 0;
}
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142