Является ли шаблонный класс лучшим вариантом и если да, то как мне это сделать?

Я пытаюсь включить указатель функции в качестве параметра в конструкторе класса, чтобы позволить любому произвольному указателю функции быть сохраненным как переменная-член в классе. В примерах кода это показано как function , назначаемая FUNCTION которая передается в конструктор и имеет тип typed_value (*)(typed_value, typed_value) . Здесь typed_value является разновидностью std::variant . Я хотел бы, чтобы это был любой вид указателя на функцию, например,

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

и так далее.

Класс ниже наследуется от NodeExtended . Я не чувствую, что функция этого класса особенно актуальна, но я рад добавить ее по запросу.

#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;
};

Реализация следует, и класс довольно ванильный, за исключением того, что extern "C" требуются для импорта конструктора в фабричный класс, который загружает их, используя dlsym как этот класс скомпилирован как разделяемая библиотека.

#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;
}

Моим первым инстинктом было шаблонирование класса, но я получаю всевозможные ошибки. Может ли кто-нибудь рассказать мне, как это сделать для этого примера, или предложить альтернативу. Например что-то вроде

#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;
}

Изменить 1:

С вышеуказанными изменениями я получаю ошибку

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) {

Однако, как видно здесь , я не верю, что решение применимо.

Всего 1 ответ


Решение превратить такой класс в шаблон в два раза. Это позволяет избежать различных ошибок, связанных с наследованием и отсутствием имени поля.

#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. Конструктор должен включать все параметры шаблона.
NonterminalNode<arity>::NonterminalNode(std::string NAME, std::vector<std::string> INPUT, std::string OUTPUT, arity FUNCTION) : NodeExtended<NonterminalNode<arity>>(NAME, INPUT, OUTPUT) {

вместо того

NonterminalNode<arity>::NonterminalNode(std::string NAME, std::vector<std::string> INPUT, std::string OUTPUT, arity FUNCTION) : NodeExtended(NAME, INPUT, OUTPUT) {
  1. Унаследованные переменные-члены должны использовать this-> как объяснено здесь
template <typename arity>
void NonterminalNode<arity>::attach(std::vector<Node *> CHILDREN) {
    this->children = CHILDREN;
}

вместо того

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

Есть идеи?

10000