0

For a refactoring project, I'm writing C++11 code and compiling with g++ on an Ubuntu 18.x system. For illustration, I have created a small toy-project to explain it.

I have a class Wall and a class Brick. Wall has a member std::array<Brick, sz> where Brick has its default, copy, and move constructors and assignment operators deleted. The size of the array is known at compile time, but it should be possible to change it by only changing the sz-variable and therefore an initializer list is not an option. Additionally, the code for Brick must not be refactored due to copyright/legal issues.

As requested, I will elaborate a bit more on the project. Currently the Brick instances are each assigned to a single Brick variable, like this:

Brick brick0 {"number 0"};
Brick brick1 {"number 1"};
Brick brick2 {"number 2"};

Part of the refactoring task is to make it easier to increase the size of the 'wall' by only changing a single variable, instead of introducing new brickXX variables. Additionally, I can say that the number of brickXXs that needs to be defined is detailed in an xml-file that has been auto-generated from synthesizing a hardware design from VHDL code. Also, there are not just bricks but also the analogy of windows, doors, lamps, and much more. In fact something like 1200 lines of individual object variables.

These limitations creates a problem with the initialization of the Brick objects in the array, but I cooked up something that I hope could be a possible solution.

My idea is to extend Brick with a class Brick_ex that implements a templated default constructor for supplying a function pointer to a count function. However, it seems that I'm missing something to finish it up and (hopefully) have it compile.

class Brick
{
public:
    const string valStr;

    explicit Brick(const string& valStrInit)
        : valStr{valStrInit}
    {}
    Brick() = delete;
    Brick(const Brick &) = delete;
    Brick(Brick &&) = delete;
    Brick &operator=(const Brick &) = delete;
    Brick &operator=(Brick &&) = delete;
};

template <int (*F)()>
class Brick_ex : public Brick
{
public:
    Brick_ex<F>()
        : Brick{"number " + to_string((*F)())}
    {}
};

class Wall
{
    int val;
public:
    int count() { return val++; }
private:
    array<Brick_ex<count>, 10> bricks;
public:
    Wall()
        : val{42}, bricks{}
    {
        for (auto &brick : bricks)
            cout << brick.valStr << endl;
    }
};

main(void)
{
    Wall a_wall;
}

When compiling I get several errors, but I think the following are the important ones:

main.cpp:41:26: error: invalid use of non-static member function ‘int Wall::count()’
     array<Brick_ex<count>, 10> arr;
                          ^
main.cpp:39:9: note: declared here
     int count() { return val++; }
         ^~~~~

I understand that using this non-static member function is a problem, because it will access the val variable of some instance that is unknown at compile time. But is there another way to define a template function that can dereference instantiated objects variables? Is there another way to achieve it?

main.cpp:41:26: error: could not convert template argument ‘Wall::count’ from ‘int (Wall::)()’ to ‘int (*)()’
     array<Brick_ex<count>, 10> bricks;
                          ^
main.cpp:41:31: error: template argument 1 is invalid
     array<Brick_ex<count>, 10> bricks;
                               ^

To me this looks like some syntax issue, so what is the correct syntax? - or is it something completely different?!?

Alternatively, instead of using a count function dereferencing an instance variable, I have thought about the possibility to use the index of each Brick_ex object instantiation. But I also haven't succeeded in figuring out how to do this?

Hope some of you can help me out...

Changelog:
Refactored class and array names -> Thank you JohnFilleau for pointing out the errors and suggesting the Wall/Brick analogy. :-)

Guzningen
  • 1
  • 2
  • Several times in your description you refer to class2 or class1, but the code has your description switched (you say class2 has its constructors deleted, this is actually class1; there are other examples). Can you do a one over and edit the question so that the description matches the code? It's hard to follow. Maybe even use toy-level class names so it's easier to follow. Maybe you could have a `Wall` which contains an array of `Brick`s? something like that. – JohnFilleau Dec 19 '21 at 13:54
  • I think the example is only destracting from what you are trying to achieve. Its a mixed bag of compile time and runtime concepts. If the function needs to be usable to generate a compile time template parameter it should be a constexpr function, and then still the template for class1_ex should be int (or better std::size_t). So WHAT is it you're trying to do? – Pepijn Kramer Dec 19 '21 at 13:56
  • In order to reliably call `Class2::count` over and over, you need to be able to tell it which `Class2` is being operated on. A non-static class method of `Class2` such as `Class2::count` has an implicit `Class2*` first parameter. – JohnFilleau Dec 19 '21 at 14:02
  • Would it suffice to have an array of `Class1` each constructed with a different `valStr`? (each with a high number than the last?) – JohnFilleau Dec 19 '21 at 14:05
  • Can be made to work by giving `Brick_ex` a static variable to be used as a counter, but that won't be thread-safe. If that's a concern, could make it `thread_local`. [Demo](https://godbolt.org/z/xK568GjbP) – Igor Tandetnik Dec 19 '21 at 15:41
  • The trick with the `static thread_local int counter` inside `Brick_ex` and the `dummy` function in the `Wall` class works like a charm! :-D Just one question: Since the dummy function is not directly used anywhere, is there then a risk that it could be optimized away, or is the side effect of initializing count enough to prevent that? – Guzningen Dec 19 '21 at 20:57

0 Answers0