Some C++ confusion: juce::Array of const structs


#1

Hi,
I just tried to do something like that:

struct MyStruct {
    double a;
    double b;
}

Result fillArrayOfStructs (Array<const MyStruct> &arrayToFill) {
    for (int i = 0; i < someValue; i++) {
        MyStruct structToFill;
        structToFill.a = foo (i);
        structToFill.b = bar (i);
        arrayToFill.add (structToFill);
    }
}

The idea is, that the structs in the array should be const, while filling them inside the function needs to be done step by step.

However this doesn’t work, there are compiler errors from Array and HeapBlock, that I don’t really understand. When changing the array to Array<myStruct>, all compiles fine. Would be nice if someone could give me a hint of what’s wrong here, I just don’t get what the compiler complains about. The documentation for Array says:

  • it must have a copy constructor and assignment operator
  • it must be able to be relocated in memory by a memcpy without this causing any problems - so objects whose functionality relies on external pointers or references to themselves can not be used.

Until now, I thought that such a simple struct would fulfill all these requirements. Am I wrong at this point? If yes, why does it only fail compiling if the struct is declared const?

These are the essential parts of the build errors:

Error:Error:Build failed with 4 errors and 0 warnings in 3s 313ms
juce_Array.h
    Error:Error:line (423)no matching function for call to 'operator new'
        Included from: ...

        Note:Note:line (856)in instantiation of member function 'juce::Array<const MyStruct, juce::DummyCriticalSection, 0>::add' requested here
            Included from:
            line (13)/Path/to/Project/Folder/Source/Main.cpp
        Note:Note:line (180)candidate function not viable: no known conversion from 'const MyStruct *' to 'const std::nothrow_t' for 2nd argument
            Included from:

... a lot of uninteresting paths
           

(632)/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory
        Note:Note:line (213)candidate function not viable: no known conversion from 'const MyStruct *' to 'void *' for 2nd argument
        Note:Note:line (179)candidate function not viable: requires single argument '__sz', but 2 arguments were provided
juce_HeapBlock.h
    Error:Error:line (264)cannot initialize a parameter of type 'void *' with an lvalue of type 'const MyStruct *'
        Included from:

...

        Note:Note:line (79)in instantiation of function template specialization 'juce::HeapBlock<const MyStruct, false>::realloc<unsigned long>' requested here
            Included from:
            
...

        Note:Note:line (98)in instantiation of member function 'juce::ArrayAllocationBase<const MyStruct, juce::DummyCriticalSection>::setAllocatedSize' requested here
        Note:Note:line (422)in instantiation of member function 'juce::ArrayAllocationBase<const MyStruct, juce::DummyCriticalSection>::ensureAllocatedSize' requested here
            Included from:
            
....

        Note:Note:line (856)in instantiation of member function 'juce::Array<const MyStruct, juce::DummyCriticalSection, 0>::add' requested here
            Included from:
            
... 

        Note:Note:line (168)passing argument to parameter '__ptr' here
            Included from:
            
...

    Error:Error:line (308)cannot initialize a parameter of type 'void *' with an lvalue of type 'const MyStruct *const'
        Included from:
        
...

        Note:Note:line (265)in instantiation of member function 'juce::HeapBlock<const MyStruct, false>::throwOnAllocationFailure' requested here
        Note:Note:line (79)in instantiation of function template specialization 'juce::HeapBlock<const MyStruct, false>::realloc<unsigned long>' requested here
            Included from:
            
...

        Note:Note:line (98)in instantiation of member function 'juce::ArrayAllocationBase<constMyStruct, juce::DummyCriticalSection>::setAllocatedSize' requested here
        Note:Note:line (422)in instantiation of member function 'juce::ArrayAllocationBase<const MyStruct, juce::DummyCriticalSection>::ensureAllocatedSize' requested here
            Included from:
           
...

        Note:Note:line (856)in instantiation of member function 'juce::Array<const MyStruct, juce::DummyCriticalSection, 0>::add' requested here
            Included from:
            
...

        Note:Note:line (30)passing argument to parameter here
            Included from:
           
...

    Error:Error:line (273)cannot initialize a parameter of type 'void *' with an lvalue of type 'const MyStruct *'
        Note:Note:line (81)in instantiation of member function 'juce::HeapBlock<const MyStruct, false>::free' requested here
            Included from:
            
...

        Note:Note:line (98)in instantiation of member function 'juce::ArrayAllocationBase<const MyStruct, juce::DummyCriticalSection>::setAllocatedSize' requested here
        Note:Note:line (422)in instantiation of member function 'juce::ArrayAllocationBase<const MyStruct, juce::DummyCriticalSection>::ensureAllocatedSize' requested here
            Included from:
            
...

        Note:Note:line (856)in instantiation of member function 'juce::Array<const MyStruct, juce::DummyCriticalSection, 0>::add' requested here
            Included from:
         
...

#2

const MyStruct doesn’t “have” an assignment operator, because const MyStruct only “has” the const member functions of MyStruct, and the default assignment operator is not const.

Basically, you can’t do this:

const MyStruct s1;
const MyStruct s2;
s2 = s1;

std::vector has similar requirements (see http://en.cppreference.com/w/cpp/container/vector for the full details), and you normally can’t have a std::vector<const T>.


#3

I guess your original idea was to prevent changing the members of MyStruct. You can achieve a similar result by distributing the const qualifier to the members, i.e.:

struct MyStruct {
  const double a;
  const double b;
};

You must then write the copy constructor and the copy assignment operator (and also some other constructor, otherwise a and b won’t be initialized).
This then allows you to write:

MyStruct s1{0.0, 42.42};
MyStruct s2{8.7, 9.8};
s2 = s1;

#4

Thanks for the clarification McMartin, seems that my assumption was wrong :wink:

Yes that was my original idea, however the example above was a bit simplified and the struct is provided by some external C library and filled by calls to functions provided by that library, so there’s no chance for me to change the struct itself. I think I’ll just live with a non-const struct and some good documentation on how to use it!