alpaka: alpaka buffers do not respect `const` correctness ?

As we try to use alpaka Buffer objects in the CMS software, we are facing a problem with const correctness due to the way the buffers are implemented.

Here is a sample program, compiled with g++ -std=c++17 -O3 -Wall -I/home/fwyzard/src/alpaka-group/alpaka/include -DALPAKA_ACC_CPU_B_SEQ_T_SEQ_ENABLED test.cc -o test:

#include <alpaka/alpaka.hpp>

template <typename TBuf>
void modify_const_buffer(TBuf buffer) {
  // buffer is not const, and can be used to modify the contents of the originally const buffer:
  buffer[42] = 0;
}

int main(void) {
  using Dim = alpaka::DimInt<1u>;
  using Idx = std::size_t;
  using Host = alpaka::AccCpuSerial<Dim, Idx>;

  const auto host = alpaka::getDevByIdx<Host>(0u);

  const auto buffer = alpaka::allocBuf<int, Idx>(host, alpaka::Vec<Dim, Idx>(64u));
  // buffer is const, so it's not possible to write into it:
  /*
test.cc: In function ‘int main()’:
test.cc:22:14: error: assignment of read-only location ‘((const alpaka::internal::ViewAccessOps<alpaka::BufCpu<int, std::integral_constant<long unsigned int, 1>, long unsigned int> >*)(& buffer))->alpaka::internal::ViewAccessOps<alpaka::BufCpu<int, std::integral_constant<long unsigned int, 1>, long unsigned int> >::operator[](42)’
   22 |   buffer[42] = 0;
  */
  //buffer[42] = 0;

  modify_const_buffer(buffer);

  return 0;
}

The issue is that the original buffer is const, but any non-const copy of buffer can be used to modify the same data.

Is this the expected behaviour ?

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Comments: 24 (24 by maintainers)

Most upvoted comments

Just thinking aloud: if we have a std::shared_ptr<T>, we can automatically convert it to a std::shared_ptr<const T>:

#include <iostream>
#include <memory>

using Buffer = std::shared_ptr<int>;
using ConstBuffer = std::shared_ptr<const int>;

Buffer make_buffer() {
  auto buffer = std::make_shared<int>(42);
  return buffer;
}

void dump_buffer(ConstBuffer buffer) {
  std::cout << (*buffer) << std::endl;
}

int main(void) {
  auto buffer = make_buffer();
  dump_buffer(buffer);

  return 0;
}

With this code we can be reasonably sure that dump_buffer(...) will not modify the content of buffer (i.e., not without using a static_cast).

Unfortunately, an alpaka buffer TBuf<T, TDim, TIdx> cannot be converted to a TBuf<const T, TDim, TIdx>, so we cannot use the same approach.

(please note, I’m not saying that having an automatic conversion from TBuf<T> to TBuf<const T> would solve all the const-ness problems, but it could help address some of them; we can try to come up with a more concrete case by next week)

So basically your very first example function template modify_const_buffer(TBuf buffer) also says it can modify buffer elements for TBuf being an alpaka buffer, similar to a foo(int *buf) saying it can modify the elements.

No, the problem is that also a function declared like

template <typename TBuf>
void modify_const_buffer(TBuf const& buffer);

can modify the contents of the buffer:

template <typename TBuf>
void modify_const_buffer(TBuf const& buffer) {
  // making a copy of a const buffer allows modifying the underlying data
  auto copy = buffer;
  copy[42] = 0;
}

So, the summary is that alpaka buffers can never give any const guarantee.

I quickly prototyped a ViewConst here: https://github.com/alpaka-group/alpaka/pull/1746.

I guess a classical solution is to have a ConstBuffer type with matching interface, so that const is part of the type and this is truly a “shallow copy const correct” container in the sense you need.

This could work, together with a change to the Buffer copy constructor: a non-const Buffer can be used to construct a Buffer, while a const Buffer can only be used to construct a ConstBuffer.

Or alternatively what was mentioned on the call, instead to have some kind of a constant view and make it so that const buffers produce constant views.

I’m actually considering the other approach we discussed this morning: to make the interface of the object that holds the buffer expose only const access to the data contained in the buffer, rather than to the buffer itself.

Okay, maybe now i realized what was missing in all my posts in this issue. What i express is of course how alpaka works as of now, and so how a user code should be written for as-is alpaka. Basically, with understanding that auto copy = buffer; is unsafe and generally should be avoided for const Buf, with const auto copy = buffer; as a safe version. I am not saying alpaka should be working like this, as it’s extremely confusing indeed, and I thought i expressed it in most my messages but then probably did it poorly

I even agree with your last point. E.g. especially with writing templated code, or being far away from alpaka. So i’m now just puzzled what we are arguing about at all

So yes, i very much agree alapka should not allow this code to work, as it’s very easy to write and make a mistake without realizing it. And it can probably be solved simply by having a function with explicit name instead of operator =, one for a “safe copy” const to const, and another one for unsafe const to non-const

From my pov alpaka buffers give const-correctness as much as their pointer semantic allows. In my view, which as we figured on the VC is different from yours, a const alpaka buffer merely means that this variable cannot be used to modify the contents. It does not mean that the contents of the container are immutable in principle. So that applies to buffer variable, but not to copy in your examples.

Yes, I understand this.

What you are saying is that a Buf containing an int should be considered like an int* or a shared_ptr<int>:

  • a const Buf is just like a int * const: the pointer itself cannot be modified, but it can be copied to a int *, and the pointed-to data can always be modified
  • a const Buf is not like a const int*, that is, a const buffer is not like a pointer to const object

the automatic cast auto copy = buffer; arguably should not compile.

This is not a cast, it’s a copy: copy and buffer are two different objects, that point to the same data.