-1

I am trying to include a function pointer as a parameter in a class constructor to allow for any arbitrary function pointer to be stored as a member variable in the class. In the code examples this is shown as function being assigned to FUNCTION which is passed into the constructor and is of type typed_value (*)(typed_value, typed_value). Here typed_value is a kind of std::variant. I would like this to be any kind of function pointer for example,

  1. type_value (*)(typed_value)
  2. type_value (*)(typed_value, typed_value)
  3. type_value (*)(typed_value, typed_value, typed_value)

and so on.

The class below inherits from NodeExtended. I do not feel the function of that class is particularly pertainant but I'm happy to add it on request.

#pragma once

#include "NodeExtended.h"
#include <iostream>

class NonterminalNode: public NodeExtended<NonterminalNode>
{
public:
    // Constructor
    NonterminalNode(std::string, std::vector<std::string>, std::string, typed_value (*)(typed_value, typed_value));

    // Destructor
    ~NonterminalNode();

    // Connectors
    void attach(std::vector<Node *>) override;

    // Function type
    typed_value (*function)(typed_value, typed_value);

    // Evaluation
    typed_value evaluate(reference_map_type & reference_map) override;
};

The implementation follows and the class is fairly vanilla except that extern "C" functions are required to import the constructor into a factory class that loads them using dlsym as this class is compiled as a shared library.

#include "NonterminalNode.h"

NonterminalNode::NonterminalNode(std::string NAME, std::vector<std::string> INPUT, std::string OUTPUT, typed_value (*FUNCTION)(typed_value, typed_value)) : NodeExtended(NAME, INPUT, OUTPUT) {
    function = FUNCTION;
}

NonterminalNode::~NonterminalNode() = default;

void NonterminalNode::attach(std::vector<Node *> CHILDREN) {
    children = CHILDREN;
}

typed_value NonterminalNode::evaluate(reference_map_type & reference_map) {
    return function(children[0]->evaluate(reference_map), children[1]->evaluate(reference_map));
}

extern "C" Node * create_object(std::string NAME, std::vector<std::string> INPUT, std::string OUTPUT, typed_value (*FUNCTION)(typed_value, typed_value))
{
    return new NonterminalNode(NAME, INPUT, OUTPUT, FUNCTION);
}

extern "C" void destroy_object(Node * object)
{
    delete object;
}

My first instinct was to template the class but I'm getting all sorts of errors thrown at me. Could somebody walk me through how to do this for this example or perhaps suggest an alternative. For example something like

#pragma once

#include "NodeExtended.h"
#include <iostream>

template <class arity>
class NonterminalNode: public NodeExtended<NonterminalNode<arity>>
{
public:
    // Constructor
    NonterminalNode(std::string, std::vector<std::string>, std::string, arity);

    // Destructor
    ~NonterminalNode();

    // Connectors
    void attach(std::vector<Node *>) override;

    // Function type
    arity function;

    // Evaluation
    typed_value evaluate(reference_map_type & reference_map) override;
};

template <class arity>
inline
NonterminalNode<arity>::NonterminalNode(std::string NAME, std::vector<std::string> INPUT, std::string OUTPUT, arity FUNCTION) : NodeExtended(NAME, INPUT, OUTPUT) {
    function = FUNCTION;
}

Edit 1:

With the above changes I receive the error

error: class ‘NonterminalNode<arity>’ does not have any field named ‘NodeExtended’ 

Occurring at:

NonterminalNode<arity>::NonterminalNode(std::string NAME, std::vector<std::string> INPUT, std::string OUTPUT, typed_value (*FUNCTION)(typed_value, typed_value)) : NodeExtended(NAME, INPUT, OUTPUT) {

Similarly seen here however, I don't believe the solution applies.

Ivor Denham-Dyson
  • 655
  • 1
  • 5
  • 24
  • 1
    Have you thought about circumventing this problem by having the function take a `std::vector` or similar? – Nico Schertler Jan 15 '20 at 19:33
  • @NicoSchertler I have considered it, that would be my last resort. Good suggestion though. This is also cause further work arounds so I'm trying to avoid it. – Ivor Denham-Dyson Jan 15 '20 at 19:38
  • 1
    Is there a reason you don't define a class to take `template ` and have a constructor along the lines of `MyClass(F, Args...)` + member `F function(Args...)`? – osuka_ Jan 15 '20 at 19:44

1 Answers1

0

The solution to turning a class like this into a template is two fold. It avoid various errors about inheritance and no field name appearing

#pragma once

#include "NodeExtended.h"
#include <iostream>

template <typename arity>
class NonterminalNode: public NodeExtended<NonterminalNode<arity>>
{
public:
    // Constructor
    NonterminalNode(std::string, std::vector<std::string>, std::string, arity);

    // Destructor
    ~NonterminalNode();

    // Connectors
    void attach(std::vector<Node *>) override;

    // Function type
    arity function;

    // Evaluation
    typed_value evaluate(reference_map_type & reference_map) override;
};

template <typename arity>
NonterminalNode<arity>::NonterminalNode(std::string NAME, std::vector<std::string> INPUT, std::string OUTPUT, arity FUNCTION) : NodeExtended<NonterminalNode<arity>>(NAME, INPUT, OUTPUT) {
    function = FUNCTION;
}

template <typename arity>
NonterminalNode<arity>::~NonterminalNode() = default;

template <typename arity>
void NonterminalNode<arity>::attach(std::vector<Node *> CHILDREN) {
    this->children = CHILDREN;
}

template <typename arity>
typed_value NonterminalNode<arity>::evaluate(reference_map_type & reference_map) {
    return function(this->children[0]->evaluate(reference_map), this->children[1]->evaluate(reference_map));
}
  1. The constructor must include all of the template parameters.
NonterminalNode<arity>::NonterminalNode(std::string NAME, std::vector<std::string> INPUT, std::string OUTPUT, arity FUNCTION) : NodeExtended<NonterminalNode<arity>>(NAME, INPUT, OUTPUT) {

instead of

NonterminalNode<arity>::NonterminalNode(std::string NAME, std::vector<std::string> INPUT, std::string OUTPUT, arity FUNCTION) : NodeExtended(NAME, INPUT, OUTPUT) {
  1. Inherited member variables should use this-> as explained here
template <typename arity>
void NonterminalNode<arity>::attach(std::vector<Node *> CHILDREN) {
    this->children = CHILDREN;
}

instead of

template <typename arity>
void NonterminalNode<arity>::attach(std::vector<Node *> CHILDREN) {
    children = CHILDREN;
}
Ivor Denham-Dyson
  • 655
  • 1
  • 5
  • 24
  • That doesn't really address the core problem of having a function pointer with a dynamic signature. – Nico Schertler Jan 15 '20 at 21:52
  • @NicoSchertler Could you elaborate further? My program now compiles and runs quite happily. This simply worked for me but it sounds like you have more insight than me. – Ivor Denham-Dyson Jan 15 '20 at 21:56
  • @NicoSchertler perhaps your referring to the ```extern "C"``` functions in which case I haven't quite made it over that hurdle. – Ivor Denham-Dyson Jan 15 '20 at 22:02
  • In my understanding, the critical part is `NonterminalNode::evaluate`, where the function is called with a varying number of arguments. You answered the other part of the question (the compilation errors), though. – Nico Schertler Jan 15 '20 at 22:02
  • @NicoSchertler I have a sneaky solution to this and I will add it in a bit. – Ivor Denham-Dyson Jan 15 '20 at 22:03