Is const-casting away const-ness of references to actual const objects permitted if they are never modified through them?

喜你入骨 提交于 2020-07-01 06:56:56

问题


I have an abstract class that declares const and non-const member functions. For the sake of discussion let's say it looks like this:

class record_interface
{
public:
   virtual ~record_interface() = default;

   virtual void set_foo(BoundedFloat) = 0;
   virtual BoundedFloat get_foo() const = 0;
};

This is used as a high-level representation of a record that has different representations when saved to disc and transferred via the wire. So most implementations just need to convert their members to the required high-level representation.

As an example of a valid implementation let's define stored_record. This is used to store the high-level record in a lossy format:

struct stored_record
{
    int16_t foo;
};

It makes sense that stored_record can implement record_interface but for various reasons it can't (eg. it needs to be trivially_copyable). We can make a wrapper that implements the interface for it:

class record_wrapper : public record_interface
{
public:
  record_wrapper(stored_record & wrapped)
    : wrapped_(wrapped) {}

  void set_foo(BoundedFloat value) final { wrapped_.foo = convert_to_int16(value); }
  BoundedFloat get_foo() const final { return convert_from_int16(wrapped_.foo); }

private:
  stored_record & wrapped_;
};

Now the problem is that we can't use the wrapper when given a const stored_record & since the wrapper stores a mutable reference. We also can't make it store a non-const reference as it won't be able to implement the non-const setter function.

Now I was wondering if it would be valid to provide a factory function that const_casts away a const stored_record & 's const but also returns a const wrapper so that the reference cannot actually be modified:

record_wrapper make_wrapper(stored_record & wrapped) {return {wrapped}; }
record_wrapper const make_wrapper(stored_record const & wrapped) { return {const_cast<stored_record &>(wrapped)}; }

EDIT: returning a const record_wrapper will not really restrict the returned value to be const, a solution can be to return a const_wrapper<record_wrapper> or something similar.

Is this a valid usage of const_cast or is it undefined behaviour due to const_casting away the const-ness of a reference to an actually const object - even though it is never modified through it.


回答1:


Per https://en.cppreference.com/w/cpp/language/const_cast:

const_cast makes it possible to form a reference or pointer to non-const type that is actually referring to a const object or a reference or pointer to non-volatile type that is actually referring to a volatile object. Modifying a const object through a non-const access path and referring to a volatile object through a non-volatile glvalue results in undefined behavior.

So, the const_cast itself is allowed (and well-defined), even though it would be undefined behavior to actually modify the object via the resulting non-const reference.




回答2:


As the other answer is perfecly clear about the validity of const-casting in your situation, one (sub-)question remains: how make your wrapper const when you want it to actually behave as const? (your edit)

I suggest providing two distinct interfaces, thus two distinct wrappers, to prevent non-const accesses to the wrapped record when it is thought about as const.
The drawback of this solution is that, in order to avoid code duplication, you have to explicitely make the mutable wrapper rely on the const wrapper (then duplicate the call, not the actual code).

Here is a simple example based on yours:

/**
  g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
      -pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
      -g -O0 -UNDEBUG -fsanitize=address,undefined
**/

#include <iostream>
#include <cstdint>

struct BoundedFloat
{
  float f;
};

struct stored_record
{
  std::int16_t foo;
};

BoundedFloat
convert_from_int16(std::int16_t v)
{
  return {float(v/100.0)};
}

std::int16_t
convert_to_int16(BoundedFloat bf)
{
  return {std::int16_t(bf.f*100.0)};
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class const_record_interface
{
public:
  virtual ~const_record_interface() = default;
  virtual BoundedFloat get_foo() const = 0;
};

class mutable_record_interface : public const_record_interface
{
public:
  virtual void set_foo(BoundedFloat) = 0;
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class const_record_wrapper : public const_record_interface
{
public:
  const_record_wrapper(const stored_record &wrapped) : wrapped_{wrapped} {}
  BoundedFloat get_foo() const final { return convert_from_int16(wrapped_.foo); }
private:
  const stored_record &wrapped_;
};

const_record_wrapper
make_wrapper(const stored_record &wrapped)
{
  return {wrapped};
}

class mutable_record_wrapper : public mutable_record_interface
{
public:
  mutable_record_wrapper(stored_record &wrapped) : wrapped_{wrapped} {}
  auto as_const() const { return make_wrapper(this->wrapped_); }
  void set_foo(BoundedFloat value) final { wrapped_.foo=convert_to_int16(value); }
  BoundedFloat get_foo() const final { return as_const().get_foo(); }
private:
  stored_record &wrapped_;
};

mutable_record_wrapper
make_wrapper(stored_record &wrapped)
{
  return {wrapped};
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

int
main()
{
  auto sr=stored_record{50};
  const auto &csr=sr;
  auto w1=make_wrapper(sr);
  auto w2=make_wrapper(csr);
  std::cout << "w1: " << w1.get_foo().f
            << "  w2: " << w2.get_foo().f << '\n';
  w1.set_foo({0.6f});
  // w2.set_foo({0.7f}); // rejected: no member named ‘set_foo'
  std::cout << "w1: " << w1.get_foo().f
            << "  w2: " << w2.get_foo().f << '\n';
  return 0;
}


来源:https://stackoverflow.com/questions/62551010/is-const-casting-away-const-ness-of-references-to-actual-const-objects-permitted

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!