Search code examples
c++arrayspointersparameter-passingstd

How to pass an std::array of pointers as const?


I want to create a std::array of pointers without declaring the type to which the pointers point to as const, so that I can change the values pointed to by the pointers of the array through dereference of these pointers.

So I don't want to do this:

#include <array>

int main()
{
    int a = 5;
    int b = 10;
    std::array<const int*, 2> arr = { &a, &b };     // with const
    *(arr[0]) = 20;                                 // Because this is not possible.
}

But this:

#include <array>

int main()
{
    int a = 5;
    int b = 10;
    std::array<int*, 2> arr = { &a, &b };           // without const
    *(arr[0]) = 20;                                 // Because now it is possible.
}

Now I want to pass this array to a function in such a way that this function can not change the values pointed to by the pointers of the array through dereference of these pointers:

#include <array>

void test(const std::array<int*, 2>& arr)
{
    *(arr[0]) = 20;     // I want this to not be possible (in this example it is)
}

int main()
{
    int a = 5;
    int b = 10;
    std::array<int*, 2> arr = { &a, &b };
    test(arr);
}

How can I do this? Since it is possible with C arrays:

void test(int const * const * const arr)
{
    *(arr[0]) = 20;     // this is not possible
}

int main()
{
    int a = 5;
    int b = 10;
    int* arr[2] = {&a, &b};
    test(arr);
}

I figured it should be possible with C++ std arrays too.

Help much appreciated. Thank you.


Solution

  • What you're asking for is unfortunately not possible.

    The underlying issue in this case is that const only propagates on the top-level.
    So making a pointer const only makes the pointer itself const but not the pointed-to object.

    This is true for any class containing pointers, not just std::array: godbolt

    std::array<int*, 2> arr = { &a, &b };
    auto const& arrref = arr;
    static_assert(std::same_as<decltype(arrref[0]), int* const&>); // not int const * const&!
    
    struct Foo {
        int* ptr;
        auto& get() const { return ptr; }
    };
    Foo foo{&a};
    auto const& fooref = foo;
    static_assert(std::same_as<decltype(fooref.get()), int* const&>); // not int const * const&!
    

    What you would need for this to work is a pointer type that propagates its constness to the pointed-to type.

    std::propagate_const (which unfortunately is still experimental as part of the library fundamentals TS) does just that: it wraps a pointer-like type so that it does propagate const to the pointed to object.

    Example: godbolt

    using std::experimental::propagate_const;
    
    void test(std::array<propagate_const<int*>, 2> const& arr)
    {
        // ill-formed:
        //*arr[0] = 20;
    }
    
    int main()
    {
        int a = 5;
        int b = 10;
        std::array<propagate_const<int*>, 2> arr = {&a, &b};
        test(arr);
    
        // well-formed
        *arr[0] = 42;
    
        return 0;
    }
    

    Another option that works with C++20 would be to use std::span.

    std::span is essentially just a pointer to the array, so you can add as much const to the element type as you want (just like in your c-array example where you decayed the array to a pointer to add constness)

    Example: godbolt

    void test(std::span<int const* const> arr)
    {
        // ill-formed:
        //*arr[0] = 20;
    }
    
    int main()
    {
        int a = 5;
        int b = 10;
        std::array<int*, 2> arr = {&a, &b};
        test(arr);
    
        // well-formed
        *arr[0] = 42;
    
        return 0;
    }