Making a const array of data and defining enum values, at the same time?

Hello,

I have a set of items in a program, whose items' properties can all be defined from const values from a .h file of data. Items are ID-based, so a lot of the code revolving around items is ID-based also, like I could call Spawn(2, 5) which would spawn item of ID '2' in an amount of '5'.

It made sense to me to make item IDs defined in an enum, in an .h file somewhere.

For example:
1
2
3
4
5
6
7
enum
{
   ITEM_ZERO = 0,
   ITEM_ONE,
   ITEM_TWO,
   MAX_UNIQUE_ITEMS
};


This makes sense, because it's safer to call "Spawn(ITEM_TWO, 5)" than to call "Spawn(2, 5)" -- in case I ever decide to insert items into the enum, using the enum titles in function calls will never fail me.

Of course those IDs aren't very helpful, so what I actually have are IDs like this:

1
2
3
4
5
6
7
enum
{
   ITEM_APPLE = 0,
   ITEM_STEAK,
   ITEM_WOOD,
   MAX_UNIQUE_ITEMS
};


So all this is fine so far -- The problem arises when I decide to define item data/properties in a const array of structs, for example:

1
2
3
4
5
6
7
8
struct ItemData
{
   char _name[20];
   int _type;
   float _weight;
   bool _food;
   char _etc;
};


I figured I could just make a const array of these, like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const ItemData ItemDataArray[MAX_UNIQUE_ITEMS] =
{
   "Apple",
   1,
   0.1f,
   true,
   'a',
   "Steak",
   0,
   0.42f,
   true,
   's',
   "Wood",
   7,
   1.2f,
   false,
   'w'
}


The problem here is that for this const array, I have to manually keep track of my IDs from the previous enum, and ensure that my item properties in the array match up with my item IDs in my enum. Which is exactly the kind of thing I wanted to avoid by using IDs in an enum in the first place. If I decide to add a new item between item#56 and item#57, I have to find the proper spot in my data array and there's a huge amount of room for human error here. Yeah, I could comment in my array like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const ItemData ItemDataArray[MAX_UNIQUE_ITEMS] =
{
   // ITEM_APPLE ---------
   "Apple",
   1,
   0.1f,
   true,
   'a',
   // ITEM_STEAK ---------
   "Steak",
   0,
   0.42f,
   true,
   's',
   // ITEM_WOOD ----------
   "Wood",
   7,
   1.2f,
   false,
   'w'
}


But this is still not great because once the list gets large, it's still prone to user error. Messing up one item will offset the whole thing.

My question is, is there any clever way I can set up my code so that I both define the initial enum value (i.e. starting at 0), AND the matching parameters for that item, in one const array? Something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const ItemData ItemDataArray[...] =
{
   ITEM_APPLE, (value = '0' and can be used like a #define)
   "Apple",
   1,
   0.1f,
   true,
   'a',
   ITEM_STEAK, (value = '1' and can be used like a #define)
   "Steak",
   0,
   0.42f,
   true,
   's',
   ITEM_WOOD, (value = '2' and can be used like a #define)
   "Wood",
   7,
   1.2f,
   false,
   'w'
}


I would need to know the array size beforehand, but that's not a big deal. My main goal is to have those integral values "set" and incrementing by 1, just like in an enum, so they can be used in function calls, and also as an index into the data array itself.

Is this possible? Or am I going about this in a poor way and there is a better way to do this?
Thank you : )
Use {}:
1
2
3
4
5
const ItemData ItemDataArray[] = // Note: The compiler calculates the array size
{
   { "Apple", 1, 0.1f, true, 'a' },
   { "Steak",  0, 0.42f, true, 's'},
...}
I would have expected that the _type of ItemData would be an enum.

Since you cannot make enum and array corresponding, it is not a good idea to use it as such. Why don't you use the struct directly:
1
2
3
4
5
6
const ItemData Apple = { "Apple", 1, 0.1f, true, 'a' };
const ItemData Steak = { "Steak",  0, 0.42f, true, 's'},

...

Spawn(Apple, 5);
If you want to spawn items randomly using an array:

const ItemData ItemDataArray[] = { Apple, Steak, ...};
Thanks for the reply and for the tips,

Forgive me if I'm wrong but I was taught that it's not great to send large objects through as parameters. Although I never explained that the ItemData struct is a bit larger in my actual project -- it contains strings for the detailed definitions, item names, etc -- it's essentially used as a lookup for info of an item ID, properties which I don't want to store in the item class itself (too big).

I was hoping for a way to use some trickery like double referencing (not sure the term), but like, an array of index IDs, to index at the correct spot, or something, like how a switch would work (where the switch case IDs dont need to go in the right order), but I was wondering if there was a way to accomplish this without having to stick it in a function (which a switch would need)
Last edited on
 is there any clever way I can set up my code so that I both define the initial enum value (i.e. starting at 0), AND the matching parameters for that item, in one const array? 

not sure about the 'clever' bit but here's an attempt based at an object factory (ref: Chapter 8, 'Modern C++ Design' by A Alexandrescu, 2001) – essentially you have a factory class template that creates instances of polymorphic types based on type identifiers (in your case the enum values) and generic product-creating functions. There are comments within the program, read and research and if anything's still unclear do gis a shout:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# include <iostream>
# include <map>
# include <functional>
# include <memory>
# include <string>
# include <vector>
# include <stdexcept>
# include <cassert>
# include <type_traits>

enum Items{Apple_enum, Steak_enum, Wood_enum};

struct Objects
{
    Objects(){}
    std::string m_name;
    int m_type;
    double m_weight;
    bool m_food;
    char m_etc;

    Objects (const std::string& name, int type, double weight, bool food, char etc)
    : m_name (name), m_type(type), m_weight(weight), m_food(food), m_etc(etc){}

    virtual void Print() const = 0;
    virtual ~Objects() {}
};


struct Apple final : public Objects
{
    using Objects::Objects;
    void Print() const{std::cout << m_name << "\n";}
};
namespace
//create an anonymous namespace to make the function ...
//... invisible from other modules when such used
{
    std::shared_ptr<Objects> CreateApple()
    //as an example, each sub-class can have its own Create() function with full details
    {
        static_assert(std::is_base_of<Objects, Apple>::value, "Apple is not a base of Objects");
        //can do similar for other sub-classes

        std::cout << "Enter name: \n"; std::string name{}; getline(std::cin, name);
        std::cout << "Enter type: \n"; int type{}; std::cin >> type;
        std::cout << "Enter weight: \n"; double weight{}; std::cin >> weight;
        std::cout << "Enter food bool value: \n"; bool food; std::cin >> food;
        //input validate bool: http://stackoverflow.com/questions/26203441/cin-and-boolean-input
        std::cout << "Enter etc value: \n"; char etc{}; std::cin >> etc;
        std::shared_ptr<Objects> p(std::make_shared<Apple>(name, type, weight, food, etc));

        return p;
    }
}
struct Steak final : public Objects
{
    using Objects::Objects;
    void Print() const {std::cout << "steak \n";}
};
namespace
{
    std::shared_ptr<Objects> CreateSteak()
    //add further details for Steak and Wood creators as required
    {
        return std::make_shared<Steak> ();
    }
}
struct Wood final : public Objects
{
    using Objects::Objects;
    void Print() const {std::cout << "wood \n";}
};
namespace
{
    std::shared_ptr<Objects> CreateWood()
    {
        return std::make_shared<Wood> ();
    }
}

template <typename Base, typename Enumeration, typename ProductCreator>
//note actual products are not a type parameter for the factory i.e. ...
//... concrete products doesn't have to be known to the factory
struct ObjectFactory
{
    void RegisterObject(const Enumeration& itm, ProductCreator classFactoryFunction)
    //register the 'type' of objects e.g. apple, steak, wood, etc
    {
        factoryFunctionRegistry[itm] = classFactoryFunction;
    }

    std::shared_ptr<Base> Create(const Enumeration& itm)
    //create instances of the 'types' registered e.g an apple, a steak, etc
    {
        std::shared_ptr<Base> instance{};
        auto it = factoryFunctionRegistry.find(itm);
        if(it != factoryFunctionRegistry.end())
        {
            instance = it->second();
        }
        else
        {
             throw std::invalid_argument( "unregistered object" );
        }

        if(instance)
        {
            return std::shared_ptr<Base>(instance);
        }
        else
        {
            return nullptr;
        }
    }
    std::map<Enumeration, ProductCreator> factoryFunctionRegistry;
};

int main()
{
    ObjectFactory<Objects, Items, std::function<std::shared_ptr<Objects>(void)>> obF{};
    //consider singleton approach here

    //registering objects
    obF.RegisterObject(Apple_enum, CreateApple);
    obF.RegisterObject(Steak_enum, CreateSteak);
    obF.RegisterObject(Wood_enum, CreateWood);

    //creating objects
    auto appleItem = obF.Create(Apple_enum);
    auto steakItem = obF.Create(Steak_enum);
    auto woodItem = obF.Create(Wood_enum);

    //container for objects' pointers
    //also check if creation was successful
    std::vector<std::shared_ptr<Objects>> myVec{};
    myVec.push_back(appleItem);
    myVec.push_back(steakItem);
    myVec.push_back(woodItem);

    std::cout << myVec.size() << "\n";
    for (const auto& elem : myVec)elem -> Print();
}
What is this program about? I find it a bit confusing that wood and apple are stored as ItemData.
How are they related? Maybe there is an easier way.
… confusing that wood and apple are stored as ItemData.  How are they related?

Given the following data-member in the OP … bool _food; … it seems to be classifying food and non-food items but OP himself/herself will be able to tell us more

It is kind of a miss that the language has 5 different arrays but still does not have an enum that ties the strings and the values together like this.

A vector of string can sort of do it.

vector<string> enumish;

enumish.pushback("first"); // 0, the index into the vector, is tied to the string.

you can either use something like that directly or wrap it in a class with some additional toys.

Here, string can be replaced with your own data of any type, of course. At the end of the day, its just cheap tricks with indexing.


… kind of a miss that the language has 5 different arrays but still does not have an enum that ties the strings and the values together …

quite right jonnin and by way of further illustration, here's a passage from my current favorite bed-time read, “Modern C++ Design”, Andrei Alexandrescu with emphasis added:

Why are C++ constructors so rigid? Why don't we have flexible means to create objects in the language itself?
Interestingly, seeking an answer to this question takes us directly to fundamental decisions about C++'s
type system. To find out why a statement such as
Base* pB = new Derived;
is so rigid, we must answer two related questions: What is a class, and what is an object? This is because
the culprit in the given statement is Derived, which is a class name, and we'd like it to be a value, that is,
an object.
In C++, classes and objects are different beasts. Classes are what the programmer creates, and objects are
what the program creates. You cannot create a new class at runtime, and you cannot create an object at
compile time. Classes don't have first-class status: You cannot copy a class, store it in a variable, or return
it from a function.
In contrast, there are languages in which classes are objects. In those languages, some objects with certain
properties are simply considered classes by convention. Consequently, in those languages, you can create
new classes at runtime, copy a class, store it in a variable, and so on. If C++ were such a language, you
could have written code like the following:
1
2
3
4
5
6
7
8
9
// Warning-this is NOT C++ 
// Assumes Class is a class that's also an object 
Class Read(const char* fileName);
Document* DocumentManager::OpenDocument(const char* fileName)
{
   Class theClass = Read(fileName);
   Document* pDoc = new theClass;
   ...
} 

That is, we could pass a variable of type Class to the new operator. In such a paradigm, passing a known
class name to new is the same as using a hardcoded constant.
Such dynamic languages trade off some type safety and performance for the sake of flexibility, because
static typing is an important source of optimization. C++ took the opposite approach, sticking to a static
type system, yet trying to provide as much flexibility as possible in this framework.

Admittedly this book was published in 2001, so if there have been any changes in this regard since then or any such changes envisaged for the near future perhaps someone will let us know
Such a construct would not perform well, for sure. I can't see it not having to do string comparisons, which are expensive. Its how you have to do it if you roll your own. Enums are high performance -- they are just compile time substitutions so tampering with them would be a bad plan, but it is still frustrating to solve the problem of the OP. Its doable, but I have never managed to do it as cleanly as I would have liked.
> is there any clever way I can set up my code so that I both define the initial enum value
> (i.e. starting at 0), AND the matching parameters for that item, in one const array?

coder777 has already given you the answer here. http://www.cplusplus.com/forum/beginner/214047/#msg997608

There is no sane reason for writing hideously convoluted code for this;
over and above everything else, a good programmer is a pragmatic programmer.

To elaborate on coder777's answer, it would be something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>

enum type
{
   ITEM_APPLE = 0,
   ITEM_STEAK,
   ITEM_WOOD,
   MAX_UNIQUE_ITEMS
};

struct item
{
    enum { NAMESZ = 20 };
    char _name[NAMESZ];
    type _type;
    double _weight;
    bool _food;
    char _etc;
};

static constexpr item get_item( type item_type )
{
    // note: assumes that the enumeration starts at zero has consecutive values
    constexpr item my_items[MAX_UNIQUE_ITEMS] =
    {
        { "Apple", ITEM_APPLE, 0.1, true, 'a' },
        { "Steak", ITEM_STEAK, 0.42, true, 's' },
        { "Wood", ITEM_WOOD, 1.2f, false, 'w' },
    };

    return my_items[item_type] ;
}

int main()
{
    for( type ty : { ITEM_STEAK, ITEM_WOOD, ITEM_APPLE } )
    {
        const auto it = get_item(ty) ; // look up based on item type enumeration
        std::cout << "item: " << it._name << " weight: " << it._weight << " kg.  ("
                  << ( it._food ? "food" : "not food" ) << ")  symbol: " << it._etc << '\n' ;
    }
}

http://coliru.stacked-crooked.com/a/414cfaa1581cb2a8
OP: I'd mentioned, in my previous post, re using the singleton approach for the factory class: http://www.cplusplus.com/forum/beginner/214047/#msg1002148

below some code on those lines with some background on the approach first – needless to say this is just one of many approaches and an illustration of what could be done. You're are best placed to pick your own methods according to your needs and inclinations:

Singleton: - ensure this class has only one instance by (a) private ctor; (b) deleted copy ctor and copy assignment operator; (c) only one static public interface that returns an instance of the class (by reference). This interface declares, and returns, a static local class instance, hence all subsequent calls to the function returns the same instance; (d) also, make the class thread safe with a mutex and lock-guard:

I'm posting the code for just the augmented singleton class here and the first line of the user interface that changes, the rest of the user interface remains unchanged from previous:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

template <typename Base, typename Enumeration, typename ProductCreator>
//note actual products are not a type parameter for the factory i.e. ...
//... concrete products doesn't have to be known to the factory
struct ObjectFactory
{
    static ObjectFactory& getInstance();
    void RegisterObject(const Enumeration& itm, ProductCreator classFactoryFunction)
    //register the 'type' of objects e.g. apple, steak, wood, etc
    {
        factoryFunctionRegistry[itm] = classFactoryFunction;
    }

    std::shared_ptr<Base> Create(const Enumeration& itm)
    //create instances of the 'types' registered e.g an apple, a steak, etc
    {
        std::shared_ptr<Base> instance{};
        auto it = factoryFunctionRegistry.find(itm);
        if(it != factoryFunctionRegistry.end())
        {
            instance = it->second();
        }
        else
        {
             throw std::invalid_argument( "unregistered object" );
        }

        if(instance)
        {
            return std::shared_ptr<Base>(instance);
        }
        else
        {
            return nullptr;
        }
    }
    
    private:
	ObjectFactory() {std::cout << "FactoryCtor\n";};
	~ObjectFactory() {std::cout << "FactoryDtor\n";};
	ObjectFactory(const ObjectFactory&) = delete;
	const ObjectFactory& operator =(const ObjectFactory&) = delete;
             std::map<Enumeration, ProductCreator> factoryFunctionRegistry;
	static std::mutex factoryMutex;
};
template <typename Base, typename Enumeration, typename ProductCreator>
std::mutex ObjectFactory<Base, Enumeration, ProductCreator>::factoryMutex{};

template <typename Base, typename Enumeration, typename ProductCreator>
ObjectFactory<Base, Enumeration, ProductCreator> &
    ObjectFactory<Base, Enumeration, ProductCreator>::getInstance()
{
	std::lock_guard<std::mutex> l(factoryMutex);
	static ObjectFactory instance;
	return instance;
}

int main()
{
    auto& obF
        = ObjectFactory<Objects, Items, std::function<std::shared_ptr<Objects>(void)>>::getInstance();
    //... rest unchanged from http://www.cplusplus.com/forum/beginner/214047/#msg1002148
}


the full program can be found here: http://cpp.sh/7754m

Hello,

Thank you for the suggestions -- I feel awful about this but I think my problem is actually much simpler than some of the solutions being offered to me, and that was due to me having a hard time explaining it.

I feel as if a full-on object factory is overkill, but I may be wrong.

To better explain the problem I'll paste some code from a previous poster:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum type
{
   ITEM_APPLE = 0,
   ITEM_STEAK,
   ITEM_WOOD,
   MAX_UNIQUE_ITEMS
};

... ...

static constexpr item get_item( type item_type )
{
    // note: assumes that the enumeration starts at zero has consecutive values
    constexpr item my_items[MAX_UNIQUE_ITEMS] =
    {
        { "Apple", ITEM_APPLE, 0.1, true, 'a' },
        { "Steak", ITEM_STEAK, 0.42, true, 's' },
        { "Wood", ITEM_WOOD, 1.2f, false, 'w' },
    };

    return my_items[item_type] ;
}


The issue is that for the enum-lookup thing to work properly, I have to ensure that the enums in the first block of code, are in the exact same order as the definitions in the second block of code. So, that means every time I add a new item -- for example, imagine I add a new item BETWEEN apple & steak, then I have to insert it in the same spot in both parts of the code. This feels, well, wrong, and also dangerous - I could easily make a mistake, once the item list gets large (which, it is getting large)

To those who asked what is the point of what I'm doing - It's a game with a (probably) basic Item system. Some item properties need to be stored uniquely in the instances of the items themselves, like their position. But a lot of their details (things the player might see when he goes to like, Inspect an item in his inventory), don't need to be unique to the instance. Things like the item name and the description. So if an item has an ID of ITEM_APPLE, then its "item name" is literally always going to be "Apple" - I don't want to store that in the item instances that are being passed around, copied, moved, etc.

I figured one way to accomplish that would be to make const structs for all of the non-changing details of the items - like the itemname - and hoped to use a lookup/enum to grab that info efficiently. The problem arises because there is a potential disconnect between the ID enum (first part) and the const definitions (second part). I wasn't sure if there was some cool way to use the item ID part (ITEM_APPLE) both as a look-up that I can pass into a function - like Spawn(ITEM_APPLE), but also somehow have my definitions part correlated in the same order without giving the user (me) the chance to jumble them up.

Sorry for the not-so-great explanation :(

BTW!!! Normally I would just use static variables in the item class itself!!! But in this case, the items aren't individual classes. There's just one Item.h / Item.cpp which functions for all items in the game (by, well, looking up the ID for those stats we're talking about to know how to behave). There are simply too many items, and they are not unique enough code-wise, to justify making individual classes for every one of them.
> I think my problem is actually much simpler than some of the solutions being offered to me
> I feel as if a full-on object factory is overkill

It is an extremely simple problem.
Using the tortuous object factory that was suggested as a solution for this would be utterly asinine.

Removing the restriction - "for the enum-lookup thing to work properly, I have to ensure that the enums in the first block of code, are in the exact same order as the definitions in the second block of code" - is quite trivial.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <cassert>

enum type // there are no restrictions on the values of the enumeration
          // other than that NOTHING must have some unique distinct value
{
   ITEM_APPLE = 0,
   ITEM_STEAK = 25,
   ITEM_WOOD = 100,
   NOTHING = -1
};

struct item
{
    enum { NAMESZ = 20 };
    char _name[NAMESZ];
    type _type;
    double _weight;
    bool _food;
    char _etc;
};

static item get_item( type item_type )
{
    // note: does not assume that the enumeration starts at zero
    //       or that the values are consecutive
    constexpr item my_items[] =
    {
        // note: these may be declared in any order
        { "Wood", ITEM_WOOD, 1.2f, false, 'w' },
        { "Steak", ITEM_STEAK, 0.42, true, 's' },
        { "Apple", ITEM_APPLE, 0.1, true, 'a' },
    };

    for( const item& it : my_items ) if( it._type == item_type ) return it ;

    return item { "nothing", NOTHING, 0, 0, 0 } ; // not found, return nothing
}

int main()
{
    for( type ty : { ITEM_STEAK, ITEM_WOOD, ITEM_APPLE } )
    {
        const auto it = get_item(ty) ; // look up based on item type enumeration
        std::cout << "item: " << it._name << " weight: " << it._weight << " kg.  ("
                  << ( it._food ? "food" : "not food" ) << ")  symbol: " << it._etc << '\n' ;
    }

    const auto no_such_item = get_item( type(546) ) ;
    assert( no_such_item._type == NOTHING ) ;
}

http://coliru.stacked-crooked.com/a/ea217049b9bcb1a4
Ahh thank you, I understand now. It iterates through the objects until the ID (in the object data) matches the ID being called and returns it.

Admittedly, I'm going to alter something, and I believe it will work -- I can iterate through the item list on game Initialize (only once), and save a permanent ID-to-ID relationship table. This will (should?) work in place of the For loop iteration, that way, item lookups will not involve any iterating

Thanks again. Saving the ID in the object data itself and checking for it to bypass the correlation problem was the key. That didn't quite click for me before. As they say, "It all makes sense now!"
Last edited on
> I can iterate through the item list on game Initialize (only once), and save a permanent
> ID-to-ID relationship table. This will (should?) work in place of the For loop iteration,
> that way, item lookups will not involve any iterating

For O(1) run-time look up, we can use std::unordered_map as the look up table.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <unordered_map>
#include <cassert>

enum type
{
   ITEM_APPLE = 0,
   ITEM_STEAK = 25,
   ITEM_WOOD = 100,
   NOTHING = -1
};

struct item
{
    enum { NAMESZ = 20 };
    char _name[NAMESZ] ;
    type _type ;
    double _weight ;
    bool _food ;
    char _etc ;
};

const item get_item( type item_type )
{
    struct hash
    {
        std::size_t operator() ( type t ) const
        { return std::hash<int>{}(t) ; }
    };

    static const std::unordered_map< type, item, hash > table
    {
        { ITEM_WOOD, item{ "Wood", ITEM_WOOD, 1.2f, false, 'w' } },
        { ITEM_STEAK, item{ "Steak", ITEM_STEAK, 0.42, true, 's' } },
        { ITEM_APPLE, item{ "Apple", ITEM_APPLE, 0.1, true, 'a' } }
    };

    const auto iter = table.find(item_type) ;
    return iter != table.end() ? iter->second : item{ "nothing", NOTHING, 0, 0, 0 } ;
}

int main()
{
    for( type ty : { ITEM_STEAK, ITEM_WOOD, ITEM_APPLE } )
    {
        const auto it = get_item(ty) ; // look up based on item type enumeration
        std::cout << "item: " << it._name << " weight: " << it._weight << " kg.  ("
                  << ( it._food ? "food" : "not food" ) << ")  symbol: " << it._etc << '\n' ;
    }

    const auto no_such_item = get_item( type(546) ) ;
    assert( no_such_item._type == NOTHING ) ;
}

http://coliru.stacked-crooked.com/a/770597278e85ea93


If the types being looked up are constants known at compile-time, we can do the look up at compile-time (ie. with zero run-time space or time overhead). For this particular program, this compile-time look up is probably just an academic exercise.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <cstddef>

enum type
{
   ITEM_APPLE = 0,
   ITEM_STEAK = 25,
   ITEM_WOOD = 100,
   NOTHING = -1
};

struct item
{
    enum { NAMESZ = 20 };
    char _name[NAMESZ] {} ;
    type _type = NOTHING ;
    double _weight = 0 ;
    bool _food = false ;
    char _etc = 0 ;

    // return true if there is a match for the type
    constexpr bool operator== ( type t ) const { return _type == t ; }
};

template < typename T, std::size_t N > struct lookup_table // compile-time lookup table
{
    const T first ;
    const lookup_table< T, N-1 > rest ;

    template < typename FIRST, typename... REST >
    constexpr lookup_table( FIRST first, REST... rest ) : first(first), rest(rest...) {}

    template < typename U > constexpr item find( U value ) const
    { return first == value ? first : rest.find(value) ; } // return first matching value
};

template < typename T > struct lookup_table< T, 0 >
{ template < typename U > constexpr T find(U) const { return T{} ; } };

template < typename FIRST, typename... REST >
constexpr auto make_lookup_table( FIRST first, REST... rest )
{ return lookup_table< FIRST, 1 + sizeof...(rest) >( first, rest... ) ; }

constexpr item get_item( type item_type )
{
    return make_lookup_table
    (
        item{ "Wood", ITEM_WOOD, 1.2f, false, 'w' },
        item{ "Steak", ITEM_STEAK, 0.42, true, 's' },
        item{ "Apple", ITEM_APPLE, 0.1, true, 'a' }
    ).find(item_type) ;
}

int main() // sanity check
{
    constexpr item stk = get_item(ITEM_STEAK) ;
    static_assert( stk._name[0] == 'S', "get_item was evaluated at compile-time" ) ;

    constexpr item nothing = get_item( type(45678) ) ;
    static_assert( nothing._type == NOTHING, "get_item was evaluated at compile-time" ) ;
}

http://coliru.stacked-crooked.com/a/f8d67055fbaaf83a
Using the tortuous object factory that was suggested as a solution for this would be utterly asinine.


I was away for a while and good to see that the 'great' JLBorges continues to spill gratuitous rudeness in my absence. Keep it up. I suppose you don't realize you have more of a reputation to protect on this forum than most and you're only demeaning yourself but such wanton attacks
Topic archived. No new replies allowed.