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

2

5

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.

Peculate answered 21/3, 2023 at 0:28 Comment(4)
"Since it is possible with C Arrays:" -- How would you do this with C arrays? (Note: allowing the array to decay to a pointer is not really doing it with C arrays, at least in my view. Plus, std::array can be transformed to the same pointer, just not implicitly.)Sent
@Sent "How would you do this with C arrays?" -- See my last code example (there was a mistake, I just edited it)Peculate
@Peculate "See my last code example" I see your last code example. However, it has test() accepting a pointer, not an array. The test() function does not use C-style arrays, so I don't accept it as a demonstration of doing something with C-style arrays. For example, given a suitably-initialized std::array<int*, 2> stdarr, you could call the last version of test via test(stdarr.data()) and have no C-style arrays in sight. My point is that the same problem exists for C-style arrays; it's just usually camouflaged by array-to-pointer decay.Sent
You could just create a const_view class that takes in any container and only exposes const access. The will require some code to write all the accessors correctly to get the const to propagate through but shouldn't be to bad.Xanthein
A
9

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;
}
Advertence answered 21/3, 2023 at 1:38 Comment(3)
Why does std::span<int const* const> work and std::span<int const*> doesn't?Tella
@Tella you always need to add const from the top level first - you can't skip a level because that would violate const correctness - i.e. std::span<int * const> would work (added const to the top level) -> std::span<int const * const> added const to the second level (also works). But skipping a level and applying const to a lower one is not allowed without an explicit cast - the rules are the same for bare pointers: godboltAdvertence
@Advertence The span version is currently clearly the best solution. Can you change the post a bit so that this is at the top? Please also include your comment regarding const. Thank you!Darcydarda
R
0

I don't think there's a way to achieve the specific requirement you mentioning. But, however, passing dereferenced integer is okay, by:

int modify(const int a)
{
    return a;
}

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

Where I cannot modify its value in the function. Although you can simply use a function to handle this issue, but it's not so out-of-the-box.

function A {
    change array to const int array
    pass const array into the function
}

or slightly modify the operator function in the array class. (overkill)

Reflectance answered 21/3, 2023 at 0:49 Comment(2)
Doesn't it use less memories? (not sure it's implementation tho)Reflectance
oh, sure, I just realize. It's using the pointer.Reflectance

© 2022 - 2024 — McMap. All rights reserved.