8

I have a struct:

typedef struct Tick {
    double open;
    double high;
    double low;
    double close;
    double ema100;
} Tick;

I would like to access a property given a key:

Tick currentTick = {44.5, 45.1, 44.2, 44.6, 44.255};
std::string key = "ema100";

std::cout << currentTick[key];

Is there a way to do this without using std::map? I imagine the answer is no, but I just want to be certain before modifying everything to use std::map and increase my memory requirements.

Chad Johnson
  • 21,215
  • 34
  • 109
  • 207

4 Answers4

10

Is there a way to do this without using std::map?

As long as you are willing to live with a series of cascading if-else statements, you can do that.

I would question the design though.

You can get there partially using tag based member variables. Sample working example:

#include <iostream>

struct OpenTag {};
struct HighTag {};
struct LowTag {};
struct CloseTag {};
struct Ema100Tag {};

struct Tick {

   template <typename Tag> struct Member
   {
      double val;

      operator double () const { return val; }

      operator double& () { return val; }
   };

   struct AllMembers : Member<OpenTag>, 
                       Member<HighTag>,
                       Member<LowTag>,
                       Member<CloseTag>,
                       Member<Ema100Tag> {};

   AllMembers data;

   template <typename Tag>
      double operator[](Tag t) const
      {
         return (Member<Tag> const&)(data);
      }

   template <typename Tag>
      double& operator[](Tag t)
      {
         return (Member<Tag>&)(data);
      }

};

int main()
{
   Tick t;
   t[OpenTag()] = 12.345;
   std::cout << t[OpenTag()] << std::endl;
}

Output:

12.345
R Sahu
  • 204,454
  • 14
  • 159
  • 270
8

The feature you're looking for is called reflection. This is not supported by native C++. You can either check for some 3rd-party libraries to do the reflection for you (but still would require lot of manual effort).

Or the other option is (as you mentioned) using std::map (or rather std::unordered_map as it would perform better) to map the name to id or offset (pointer) of the field within the class and then via switch statement (in former case) or directly using the pointer (in the latter case) modify the field value.

Zbynek Vyskovsky - kvr000
  • 18,186
  • 3
  • 35
  • 43
2

You can do that using metadata available at compile time. However, we must do it manually:

template<typename Class, typename T>
struct Property {
    constexpr Property(T Class::*aMember, const char* aName) : member{aMember}, name{aName} {}

    using Type = T;

    T Class::*member;
    const char* name;
};

template<typename Class, typename T>
constexpr auto makeProperty(T Class::*member, const char* name) {
    return Property<Class, T>{member, name};
}

Now we have a class that can hold our desired metadata. Here's how to use it:

struct Dog {
    constexpr static auto properties = std::make_tuple(
        makeProperty(&Dog::barkType, "barkType"),
        makeProperty(&Dog::color, "color")
    );

private:
    std::string barkType;
    std::string color;
};

Now we can do iteration on it by recursion:

template<std::size_t iteration, typename T, typename U>
void accessGetByString(T&& object, std::string name, U&& access) {
    // get the property
    constexpr auto property = std::get<iteration>(std::decay_t<T>::properties);

    if (name == property.name) {
        std::forward<U>(access)(std::forward<T>(object).*(property.member));
    }
}

template<std::size_t iteration, typename T, typename U>
std::enable_if_t<(iteration > 0)>
getByStringIteration(T&& object, std::string name, U&& access) {
    accessGetByString<iteration>(std::forward<T>(object), name, std::forward<U>(access));
    // next iteration
    getByStringIteration<iteration - 1>(std::forward<T>(object), name, std::forward<U>(access));
}

template<std::size_t iteration, typename T, typename U>
std::enable_if_t<(iteration == 0)>
getByStringIteration(T&& object, std::string name, U&& access) {
    accessGetByString<iteration>(std::forward<T>(object), name, std::forward<U>(access));
}

template<typename T, typename U>
void getByString(T&& object, std::string name, U&& access) {
    getByStringIteration<std::tuple_size<decltype(std::decay_t<T>::properties)>::value - 1>(
        std::forward<T>(object),
        name,
        std::forward<U>(access)
    );
}

Then finally, you can use this tool like:

struct MyAccess {
    void operator()(int i) { cout << "got int " << i << endl; }
    void operator()(double f) { cout << "got double " << f << endl; }
    void operator()(std::string s) { cout << "got string " << s << endl; }
}

Dog myDog;

getByString(myDog, "color", MyAccess{});

This for sure could by simplified with overloaded lambda. To know more about overloaded lambda, see this blog post

The original code was taken from that answer: C++ JSON Serialization

There's a proposal to make things like this easier. This is covered by P0255r0

Community
  • 1
  • 1
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
1

Honestly i think the use of an overloaded operator[] and if-else statement is all you really need. Given any class, are their really that many members? The other solutions in my opinion provide too much overhead for such a simple task. I would just do something like this:

template <typename T>
T& operator[](const std::string& key) 
{
    if (key == ...)
        // do something
}
Nowhere Man
  • 475
  • 3
  • 9
  • In my case there will be over 100 members, so for my particular scenario, an `if-else` or `switch` block would be tedious. – Chad Johnson Apr 08 '16 at 20:48