0

I have a struct Recipe that houses a static array of type Ingredient, I want to construct it with a variadic template to fill the array with an arbitrary amount. I've looked at other questions posted here, mainly: Create static array with variadic templates, but when the array is filled with {args...}, the data isn't what was put in. This is in msvc.

struct Ingredient
{
    constexpr Ingredient(U16 id, U16 amount = 1, bool consumed = true) : id{ id }, amount{ amount }, consumed{ consumed } {}

    U16 id;
    U16 amount;
    bool consumed;
};
struct Recipe
{
    template<typename... Args>
    constexpr Recipe(U16 result, U16 amount, U8 benchLevel, const Args&... args) :
        result{ result }, amount{ amount }, benchLevel{ benchLevel }, ingredientCount{ sizeof...(args) }, ingredients{ {args...} }
    {
    }

    U16 result;
    U16 amount;
    U8 benchLevel;

    U16 ingredientCount;
    Ingredient ingredients[];
};
class Items
{
public:
    static const Item* GetItem(U16 id) { return items[id]; }
    static const Recipe** GetRecipes() { return recipes; }

private:
    static const Item* items[];
    static const Recipe* recipes[];

    Items() = delete;
};
inline const Recipe* Items::recipes[]
{
    new Recipe(21, 1, 0, Ingredient{11}, Ingredient{12}),
    new Recipe(22, 1, 0, Ingredient{11}, Ingredient{12}),

    nullptr
};

Usage code:

void FillCraftingMenu()
{
    const Recipe** recipes = Items::GetRecipes();

    const Recipe* recipe = recipes[0];

    U16 i = 0;
    while (recipe)
    {
        bool found = true;

        for (U16 j = 0; j < recipe->ingredientCount; ++j)
        {
            found &= inventory->ContainsItem(recipe->ingredients[j].id, recipe->ingredients[j].amount);
        }

        if (found)
        {
            //TODO: put up recipe
            Logger::Debug("Recipe found: {}", recipe->result);
        }

        recipe = recipes[++i];
    }
}

the ingredients list in the recipes becomes [0] {id=65021, amount=65021, consumed=false} [1] {id=0, amount=0, consumed=false}

  • why are all these things static? – Neil Butterworth Nov 04 '22 at 16:18
  • Please use standard types or define what your types are. Also, flexible array members are not standard C++. So add the compiler you are using as well. – user17732522 Nov 04 '22 at 16:20
  • Items is a class to be accessed anywhere without an instance, it's pretty much a database – Zachary Peterson Nov 04 '22 at 16:20
  • Please explain what this code should do. Best form is failing test (which you expect to pass, compilation error is also a test failure). So please provide example o usage which will visualize fist paragraph from your question. – Marek R Nov 04 '22 at 16:22
  • The only form of flexible array member I am aware of requires manually allocating sufficient space for the whole structure+array before creating the object. You are not doing that anywhere. You would need to use `malloc` or `operator new` first to allocate the memory, then placement-new the object into it (assuming this form of flexible array member is supported by the compiler in the first place, which I don't know about). Why do you use such an unusual construction though? Why not use a `std::vector`? – user17732522 Nov 04 '22 at 16:34
  • `Ingredient ingredients[];` is not a fixed size array. For a fixed size array you'd need a template class. Your only option here is using something like `std::vector` or allocate the array via `new[]`. – fabian Nov 04 '22 at 16:35
  • So I guess I can switch to Ingredient* ingredients and initialize it with new[], but then how would I fill the array? I can't access args with a subscript operator as far as I know. – Zachary Peterson Nov 04 '22 at 16:40
  • @ZacharyPeterson Why `Ingredient*` instead of `std::vector`? You literally only have to replace `Ingredient ingredients[];` with `std::vector ingredients;`, replace the redundant `{` and `}` in the initializer, and it will work. You can also then get rid of `ingredientCount` and use `ingredients.size()` instead. – user17732522 Nov 04 '22 at 16:43
  • I don't use std containers, I usually make my own, I know I'm stubborn but that's how it is. – Zachary Peterson Nov 04 '22 at 16:44
  • @ZacharyPeterson Then implement your own version of `std::vector` with the features you need and use that. – user17732522 Nov 04 '22 at 16:45
  • I have my own version of Vector, but we run into the exact same problem, how do you fill the array with the data from args – Zachary Peterson Nov 04 '22 at 16:46
  • @ZacharyPeterson You give the `Vector` class a `std::initializer_list` constructor like `std::vector` has. – user17732522 Nov 04 '22 at 16:47

1 Answers1

0

So the solution I found worked well for me was:

template<typename... Args>
constexpr Recipe(U16 result, U16 amount, U8 benchLevel, const Args&... args) :
    result{ result }, amount{ amount }, benchLevel{ benchLevel }, ingredientCount{ sizeof...(args) }, ingredients{ (Ingredient*)malloc(sizeof(Ingredient) * ingredientCount) }
{
    U16 i = 0;
    (void(ingredients[i++] = args), ...);
}