0

With the use of constant expressions, parameter packs, lambdas, move semantics, standard containers, and std::any I can easily enough create the following design pattern:

#include <utility>
#include <vector>
#include <any>

constexpr auto assign_properties = [](auto&&... t) { 
    return std::vector<std::any>{ std::move(t)... };  
};  

That would create an arbitrary list of properties or attributes without the need of defining a struct or class object.

Simply enough it can be used in the following manner:

{
     auto props = assign_properties(1, 3.1f, 6.4, 'a');
}

Now, to access the type-safe data within this container one would have to know each element's type in advance and would have to extract them in the following manner:

{
    auto a = std::any_cast<int>( props[0] );
    auto b = std::any_cast<float>( props[1] );
    auto c = std::any_cast<double>( props[2] );
    auto d = std::any_cast<char>( props[3] );
}

Okay, this is simple and fair enough...

Let's say I want to incorporate some kind of mechanism such as this within a class hierarchy that will preserve polymorphic behaviors...

Within the class hierarchy, I may want to have an abstract base type that all types are derived from. As for each derived or subclass, they may contain a set of properties or attributes that describes either its behavior or state, however, each subclass may have a different amount of attributes with various different types...

For example:

class Animal {
protected:
    Animal(); // Abstract
    std::vector<std::any> attributes_;
};
     
class Cat : public Animal {  
public:
    // ...    
};

class Dog : public Animal {
public:
   // ...
};

class Eagle : public Animal {
public:
   // ...
};

Now, I'm not showing any polymorphic behavior, virtual, or purely virtual methods here since they are not necessary towards the intent of my question which I will get to briefly... somewhere else within some other code these objects are being created and then populated...

struct Fur;    
struct Fangs;
struct Claws;
struct Feathers;
struct Talons;
struct Scales;

{
   Cat c1;
   c1.populate_attributes(assign_properties( "Fluffy", 9, Fur, Fangs, Claws));

   Dog d1;
   d1.populate_attributes(assign_properties( "Ruff", 3, Fur, Fangs, Claws ));
   
   Eagle e1;
   e1.populate_attriutes(assign_properties( Fangs, Claws, Feathers, Talons );
}

Simple enough... Now, as I stated above, accessing the values within these vectors from std::any requires you to know the type ahead of time in order to use std::any_cast to extract its value in a type-safe manner.

Let's say that I plan on using this while parsing a file to create and populate my class data types with their properties. Then somewhere else in some other function or code block, these elements need to be accessed and I don't know what their types are... std::any within this context does not provide any facilities nor helps me with this process.

{ 
    std::vector<Animals*> animals { Constructed & Populated; }
}
// some other code block
{
    auto a, b, c, d, ...;
    a = animals[0];
    b = animals[1];
    c = animals[2];
    d = animals[3];
    
    // Above we got each individual animal from our external source vector...
    
    auto e,f,g,h, ...;
    e = a[0]; // doesn't give the type and its value, e will be `std::any`.
    e = std::any_cast<auto>( e[0] ); // will fail to compile since `<T>` must
                                     // be known at compile time.
    e = std::any_cast<?>( e[0] );    // ... and this is where I'm stuck
                                     // I need some form of extraction here!
                                     // and I would like for it to be automatic and generic.
}        

If the user doesn't know the types ahead of time and needs to access these values within a type-safe context, is there an easy way to automate this retrieval or extraction process with the current implementation that is described above? If not, what can be done to modify the above code to make it work as intended, or is there any other mechanism or construct that could be used in place of this to implement a similar design pattern while preserving or providing the behavior that I have expressed?

cigien
  • 57,834
  • 11
  • 73
  • 112
Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • 1
    Have you looked at boost::property_tree ? You can view/use a ptree a bit like a xml or json, and event insert the metadata you'd need to do that. Even "serialize" structures using operators << and >>. – Michaël Roy Jan 28 '21 at 17:05
  • @MichaëlRoy I don't use Boost, however, I appreciate the feedback that boost does have some kind of implementation that supports my desired behavior. However, I'm just trying to simply write my own via the `STL`... – Francis Cugler Jan 28 '21 at 17:13
  • @FrancisCugler: "*these elements need to be accessed and I don't know what their types are...*" Then you can't access them. C++ is a statically typed language, and bypassing that is not particularly viable. Even if it were `void*`s, you wouldn't be able to do anything reasonable with them without first casting them back to a pointer to the right type. If you have a limited set of types that the values could contain, you can use a `variant` or a similar type. But C++ doesn't let you use an object without knowing its type. – Nicol Bolas Jan 29 '21 at 23:27
  • @NicolBolas I understand that C++ is a statically typed language... I was just wondering if there were any known tools that could mitigate this limitation. I guess the one option that I could do is, within the source text or binary file that is to be read into the parser, I could have the labels in there for the objects' property types and pull this information from the file and store them into a `vector` and then use each object's `vector` to determine its type. – Francis Cugler Jan 30 '21 at 00:30
  • @FrancisCugler: And how do you plan to convert a runtime string into a compile-time typename? You can't even convert a compile-time string into a compile-time typename. – Nicol Bolas Jan 30 '21 at 00:42
  • @NicolBolas Yeah, it leans towards an X/Y problem... – Francis Cugler Jan 30 '21 at 00:42
  • 2
    It's not so much an XY problem, as a fundamental, intractable problem in any statically typed language. There are many ways to spell it, but at the bottom it's the same issue: you have to know at compile time what the type of stuff is. – Nicol Bolas Jan 30 '21 at 00:47
  • @NicolBolas I may have to do what I was trying to avoid originally... Have a structure within each subclass... Now, I could have the nested struct have the same name in every class where each has its own fields... but that's also a lot of code duplication... I also don't want to have the property class either in the parent class or outside... if the latter was the case then that struct might end up having 50 members to cover all possible attributes... then any time an instance of a subclass was to be created, this leads to unneccessary memory consumption. – Francis Cugler Jan 30 '21 at 00:47
  • Or you could just use a serialization library. – Nicol Bolas Jan 30 '21 at 00:48
  • @NicolBolas Okay, but is one available in the STL as opposed to a 3rd party library such as boost? – Francis Cugler Jan 30 '21 at 00:49
  • @NicolBolas It would also help to make my parser much easier to write too... – Francis Cugler Jan 30 '21 at 00:51
  • @FrancisCugler: "*Okay, but is one available in the STL as opposed to a 3rd party library such as boost?*" The C++ standard library has no such thing and is unlikely to in the future. Furthermore, you should not treat the C++ standard library as if it is all that is available for C++. The standard library is not designed under the expectation that it is all you use, and you would be unable to write very much in C++ if that were the only library you used. – Nicol Bolas Jan 30 '21 at 02:13
  • @NicolBolas It's not the only library I use, I just don't use boost's it conflicts with too many things on my machine... I've tried using it before, but there are things in boost that fail to compile or build due to the fact that I'm on a Window's machine and some of those libraries are GNU (Linux only) based... That's why I was asking if anything was available within the STL... – Francis Cugler Jan 30 '21 at 04:43
  • @FrancisCugler: All of Boost compiles on Windows. If you're unable to compile it, it would only be due to something specific to your machine. – Nicol Bolas Jan 30 '21 at 05:49
  • @NicolBolas A few years back I was trying to use a feature from Boost, and it included a header file from GNU's MPFR library for working with Large Integers. It was giving me issues within Visual Studio as I don't use Mingw... You can refer to this older Q/A to where I had issues with it, https://stackoverflow.com/q/49795317/1757805 I ended up abandoning that project due to all of the conflicts and the failure to properly support some of the GNU libraries within Visual Studio or on Windows such as the MPFR library for large integers. And these were dependencies that boost relied on. – Francis Cugler Jan 30 '21 at 10:19

0 Answers0