1

Is it possible to downcast between std::atomic_ref<T> and std::atomic_ref<U> where U is a subclass of T?

I tried the following code which didn't work.

template<typename T>
std::atomic_ref<T> World::getComponentByID(componentInstanceId uuid) const {
    static componentTypeId key = stringHash(typeid(T).name());
    return components.at(uuid);
}

The template parameter T was position (a subclass of Component). components is a map of componentInstanceIds to Components .

error C2440: 'return': cannot convert from 'const std::atomic_ref<Component>' to 'std::atomic_ref<Position>

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
  • 1
    I think you mean "upcasting" rather than "downcasting"? – ruakh Oct 05 '21 at 01:02
  • I am casting from a superclass `Component` to a subclass `Position` @ruakh. – RazeLighter777 Oct 05 '21 at 01:05
  • I see. In that case, I'm kind of surprised that you tried the code that you did, seeing as C++ *never* allows implicit downcasts so far as I'm aware. Am I missing something? – ruakh Oct 05 '21 at 01:07
  • Yeah. I am trying to do an explicit downcast, but I can't find out how to do that either. – RazeLighter777 Oct 05 '21 at 01:09
  • Ah, OK, understood. – ruakh Oct 05 '21 at 01:19
  • you should provide a reproducible example. – Serge Oct 05 '21 at 01:46
  • I doubt this is possible at the moment. To make the reference atomic, accessing an `atomic_ref` object creates a temporary object under the hood. And since you can’t get the reference of a temporary object, you can’t get a `atomic_ref` from `atomic_ref`. Unless the library has direct support for the downcasting feature, which stl doesn’t have it, I don’t think this would be possible. – Ranoiaetep Oct 05 '21 at 02:56
  • 2
    [From cppreference](https://en.cppreference.com/w/cpp/atomic/atomic_ref): "No subobject of an object referenced by an `atomic_ref` object may be concurrently referenced by any other `atomic_ref` object." This means it would be undefined behavior to have an `atomic_ref` referencing an object of derived class and another referencing its base class subobject, as the cast you envision would require. – Igor Tandetnik Oct 05 '21 at 03:37
  • Is there any other way to achieve what I desire? Atomic access to an object with downcasting? @IgorTandetnik – RazeLighter777 Oct 05 '21 at 03:44
  • 3
    What you are trying to do doesn't make any sense to me. Sounds like an [XY problem](https://xyproblem.info/). Why are you holding `components` by `atomic_ref` - is there concurrent access to them somehow? Where and how do you store actual `Component` objects that those references refer to? Show a [mcve]. – Igor Tandetnik Oct 05 '21 at 03:53

1 Answers1

2

Usage of atomic_ref this way does not make sense. (Even in lock_free cases that make sense in assembly language, the ISO C++ standard doesn't expose that functionality.)

As @Igor Tandetnik pointed out, "No subobject of an object referenced by an atomic_ref object may be concurrently referenced by any other atomic_ref object."

One of reasons this rule exists is that for non-lock-free atomic_ref it implements pool of mutexes, so having sub-object with different pointer value will get you into a different mutex for a subobject, or even a mutex from a different pool, or it may happen that sub-object is lock-free, but the bigger object is lock-based.

Additionally, possible pointer adjustments may defeat alignment, and it is mandatory for value referenced by atomic_ref to respect atomic_ref<T>::required_alignment.


atomic_ref is not a general purpose facility. Perhaps you just need to protect your objects with std::mutex or std::shared_mutex instead.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
  • 1
    *Usage of atomic_ref this way does not make sense.* If both the full object and the sub-object are lock_free, then It does make sense in asm on real CPUs, and would work as a hack in C++ if written correctly. Instead of saying it didn't make sense, I'd say this is asm functionality that is not exposed by ISO C++. – Peter Cordes Oct 05 '21 at 18:05
  • Similar problem to [How can I implement ABA counter with c++11 CAS?](https://stackoverflow.com/a/38991835) where you want efficient reads, but current compilers won't optimize a read of a 16-byte `atomic` and then only using 1 member into just an 8-byte load. I hacked around it with a `union` between an `atomic<16byte>` and a struct of two `atomic<8-byte>` which is obviously not well-defined in ISO C++ but does compile to good asm. All because compilers suck at `16byte.load().member`. – Peter Cordes Oct 05 '21 at 18:06