3220. P0558 broke conforming C++14 uses of atomic shared_ptr

Section: 32.5.2 [atomics.syn] Status: New Submitter: Casey Carter Opened: 2019-06-11 Last modified: 2020-09-06

Priority: 3

View other active issues in [atomics.syn].

View all other issues in [atomics.syn].

View all issues with New status.

Discussion:

This well-formed C++14 program:

#include <atomic>
#include <memory>

struct Abstract { virtual void test() = 0; };
struct Concrete : Abstract { virtual void test() override {} };

int main() {
  std::shared_ptr<Abstract> ptr;
  std::atomic_store<Abstract>(&ptr, std::make_shared<Concrete>());
}

is ill-formed in C++17. P0558 changed the non-member non-shared_ptr atomic functions to avoid deducing from their second argument, e.g. C++14 atomic_store:

template<class T> void atomic_store(atomic<T>*, T); // #1

became C++17 atomic_store:

template<class T> void atomic_store(atomic<T>*, typename atomic<T>::value_type); // #2

The program intends to call the "other" atomic_store from 99 [depr.util.smartptr.shared.atomic]:

template<class T> void atomic_store(shared_ptr<T>*, shared_ptr<T>); // #3

In C++14, the call expression in the sample program — std::atomic_store<Abstract>(&ptr, std::make_shared<Concrete>()) — selects overload #3; overload #1 fails to be viable due to the lack of conversions from shared_ptr<Abstract>* to atomic<Abstract>* and from shared_ptr<Concrete> to Abstract. In C++17, overload #2 doesn't get to the point of considering argument conversions: when we try to generate the declaration of the specialization for T = Abstract we must instantiate atomic<Abstract> in order to substitute typename atomic<Abstract>::value_type, but doing so violates the requirement in [atomics.types.generic] p1 that "The type of the template argument T shall be trivially copyable"

The fix is fairly straightforward since atomic<T>::value_type is always an alias for T: for those non-member atomic functions with overloads defined in 99 [depr.util.smartptr.shared.atomic], use a different form to require that T in the type of the second parameter is non-deduced.

[2019-07 Issue Prioritization]

Priority to 3 after discussion on the reflector.

Proposed resolution:

This wording is relative to N4810.

  1. Modify 32.5.2 [atomics.syn], header <atomic> synopsis, as indicated:

    […]
    // 32.5.9 [atomics.nonmembers], non-member functions
    […]
    template<class T>
      void atomic_store(volatile atomic<T>*, typename atomic<T>::value_typetype_identity_t<T>) noexcept;
    template<class T>
      void atomic_store(atomic<T>*, typename atomic<T>::value_typetype_identity_t<T>) noexcept;
    template<class T>
      void atomic_store_explicit(volatile atomic<T>*, typename atomic<T>::value_typetype_identity_t<T>,
                                 memory_order) noexcept;
    template<class T>
      void atomic_store_explicit(atomic<T>*, typename atomic<T>::value_typetype_identity_t<T>,
                                 memory_order) noexcept;
    […]
    template<class T>
      T atomic_exchange(volatile atomic<T>*, typename atomic<T>::value_typetype_identity_t<T>) noexcept;
    template<class T>
      T atomic_exchange(atomic<T>*, typename atomic<T>::value_typetype_identity_t<T>) noexcept;
    template<class T>
      T atomic_exchange_explicit(volatile atomic<T>*, typename atomic<T>::value_typetype_identity_t<T>,
                                 memory_order) noexcept;
    template<class T>
      T atomic_exchange_explicit(atomic<T>*, typename atomic<T>::value_typetype_identity_t<T>,
                                 memory_order) noexcept;
    template<class T>
      bool atomic_compare_exchange_weak(volatile atomic<T>*,
                                        typename atomic<T>::value_typetype_identity_t<T>*,
                                        typename atomic<T>::value_typetype_identity_t<T>) noexcept;
    template<class T>
      bool atomic_compare_exchange_weak(atomic<T>*,
                                        typename atomic<T>::value_typetype_identity_t<T>*,
                                        typename atomic<T>::value_typetype_identity_t<T>) noexcept;
    template<class T>
      bool atomic_compare_exchange_strong(volatile atomic<T>*,
                                          typename atomic<T>::value_typetype_identity_t<T>*,
                                          typename atomic<T>::value_typetype_identity_t<T>) noexcept;
    template<class T>
      bool atomic_compare_exchange_strong(atomic<T>*,
                                          typename atomic<T>::value_typetype_identity_t<T>*,
                                          typename atomic<T>::value_typetype_identity_t<T>) noexcept;
    template<class T>
      bool atomic_compare_exchange_weak_explicit(volatile atomic<T>*,
                                                 typename atomic<T>::value_typetype_identity_t<T>*,
                                                 typename atomic<T>::value_typetype_identity_t<T>,
                                                 memory_order, memory_order) noexcept;
    template<class T>
      bool atomic_compare_exchange_weak_explicit(atomic<T>*,
                                                 typename atomic<T>::value_typetype_identity_t<T>*,
                                                 typename atomic<T>::value_typetype_identity_t<T>,
                                                 memory_order, memory_order) noexcept;
    template<class T>
      bool atomic_compare_exchange_strong_explicit(volatile atomic<T>*,
                                                   typename atomic<T>::value_typetype_identity_t<T>*,
                                                   typename atomic<T>::value_typetype_identity_t<T>,
                                                   memory_order, memory_order) noexcept;
    template<class T>
      bool atomic_compare_exchange_strong_explicit(atomic<T>*,
                                                   typename atomic<T>::value_typetype_identity_t<T>*,
                                                   typename atomic<T>::value_typetype_identity_t<T>,
                                                   memory_order, memory_order) noexcept;
    
  2. Modify 32.5.9 [atomics.nonmembers] as indicated:

    -1- A non-member function template whose name matches the pattern atomic_f or the pattern atomic_f_explicit invokes the member function f, with the value of the first parameter as the object expression and the values of the remaining parameters (if any) as the arguments of the member function call, in order. An argument for a parameter of type atomic<T>::value_type* or type_identity_t<T>* is dereferenced when passed to the member function call. If no such member function exists, the program is ill-formed.