simeondermaats
simeondermaats

Reputation: 423

Can I have a "type of types" in C++?

I'm quite new to C++, but I've been trying to diversify my skillset a bit during the lockdown. I'm attempting to write a node-based sound processing tool using SFML. I want to have a struct to contain all of my nodes' inputs and outputs. However, I obviously can't connect any type together. I'm looking to do something similar to what Blender does with its node types. The following screen recording shows what I mean: the green output type is incompatible with other input types. This screen recording

I thought that a struct NodeInOut could be a useful solution to this problem: I'd have a type that I assign whenever I create a new Node, and in the logic for connecting Nodes together I make sure that incompatible types aren't connectable. However, I'll need to pass in a type whenever I create a new Node definition.

My idea for the Node class is that it would be structured a bit like this hastily made diagram.

this hastily made diagram

Does anyone have an idea on how to do this, or how to structure it differently such that this isn't an issue?

I haven't written any code yet besides the SFML boilerplate stuff.

Upvotes: 4

Views: 122

Answers (3)

rajdakin
rajdakin

Reputation: 84

As discussed in the comments below HolyBlackCat's answer, here is my alternative:

  • Each nodes are a member of the (final, not virtual) Node class

  • This Node class has an std::vector<Input> and an std::vector<Output>, which represents each input and output of the node (they could probably be made const if you try hard enough)

  • You add a private variable called update which has type std::function<void(Node &)> or equivalent (so you could have lambdas, for example), which is a user-defined update method to set the outputs depending on the inputs, and a public function called Update, which is a function that calls the update of this node and the Update of the following nodes (since they need to update too)

  • Then for the Outputs you need to have a function to set the value (maybe a templated function? Then you check if the type is correct and get a compatible type, maybe use std::variants to store the value), and the list of all Inputs connected to it (so an std::vector<Node*, unsigned> maybe? Or you can also replace the Node* by a std::weak_ptr<Node>)

  • As for the inputs you need to be able to connect them to one Output so either an Output* or an std::weak_ptr<Output> should work, but since you need to be able to have constants you can either:

    1. Also have an std::variant to store the default value, or
    2. You can create new "invisible" Nodes with no input and one output for each dangling inputs, or
    3. You create an invisible Node for each visible ones, which has no input and as many outputs as there are inputs for the target Node (and connect them one-to-one).

This is maybe (probably) not the best solution, but at least it should work.

Upvotes: 1

HolyBlackCat
HolyBlackCat

Reputation: 96166

If I understand the question correctly, something like this should work:

  • Specific nodes inherit from an abstract class Node.

  • class Node contains pure virtual functions to list the input/output points:

    virtual std::size_t InputCount() const = 0;
    virtual const Input &GetInput(std::size_t i) const = 0;
    Input &GetInput(std::size_t i) {return const_cast<Input &>(std::as_const(*this).GetInput(i));}
    
    virtual std::size_t OutputCount() const = 0;
    virtual const Output &GetOutput(std::size_t i) const = 0;
    Output &GetOutput(std::size_t i) {return const_cast<Output &>(std::as_const(*this).GetOutput(i));}
    

    And probably something like virtual void Update() = 0;, DrawGui, etc.

  • Classes derived from Node have Input and Output objects as members, and override GetInput and GetOutput to return those members.

    (Bonus: you might be able to do generate the functions automatically using a reflection library such as magic_get.)

  • class Input will have to somehow point to the Output it's connected to, probably by storing a pointer (std::weak_ptr?) to Node owning the connected Output, along with the index of that Output in the node.

  • class Output should at least contain float value; (or something else, depending on what values you want to support). Maybe you will want to store a list of connected Inputs in there (where each element would be a pointer to Node plus the index of the Input in it).

Upvotes: 1

Acorn
Acorn

Reputation: 26066

Yes, you can have a "type of types".

However, notice the user connects nodes at runtime. Thus this is a runtime problem, not something you can solve at compile time.

In other words, simply use runtime values that contain whatever types your nodes accept/send and compare them as needed.

Upvotes: 2

Related Questions