2236. kill_dependency unconditionally noexcept

Section: 32.5.2 [atomics.syn], 32.5.4 [atomics.order] Status: SG1 Submitter: Daniel Krügler Opened: 2013-01-19 Last modified: 2016-01-28

Priority: Not Prioritized

View other active issues in [atomics.syn].

View all other issues in [atomics.syn].

View all issues with SG1 status.

Discussion:

The "magic" kill_dependency function is a function without any constraints on the template parameter T and is specified as

template <class T>
T kill_dependency(T y) noexcept;

-14- Effects: The argument does not carry a dependency to the return value (1.10).

-15- Returns: y.

I wonder whether the unconditional noexcept is really intended here: Assume we have some type U that has a potentially throwing move constructor (or it has a potentially throwing copy constructor and no move constructor), for any "normal" function template with the same signature and the same effects (modulo the dependency magic) this would mean that it cannot safely be declared noexcept because of the return statement being part of the complete function call affected by noexcept (The by-value function argument is irrelevant in this context). In other words it seems that a function call such as

struct S {
  ...
  S(const S& r) { if(some condition) throw Something(); }
  ...
};

int main() {
  S s1 = ...;
  S s2 = std::kill_dependency(s1);
}

would be required to call std::terminate if the copy constructor of S throws during the return of std::kill_dependency.

To require copy elision for this already magic function would look like a low-hanging fruit to solve this problem, but this case is not covered by current copy elision rules see 12.8 p31 b1:

"— in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value".

Some options come into my mind:

  1. Make the exception-specification a constrained one in regard via std::is_nothrow_move_constructible:

    template <class T>
    T kill_dependency(T y) noexcept(see below);
    

    This is similar to the approach taken for function templates such as std::swap.

  2. Use perfect forwarding (This needs further wording to correct the effects):

    template <class T>
    T&& kill_dependency(T&& y) noexcept;
    
  3. Impose constraints on the template arguments in regard to throwing exceptions while copying/moving.

  4. Keep the state as it is but possibly add a note about a call of std::terminate in above scenario.

A second problem is that the current wording is not clear whether it is well-defined to call the function with types that are reference types, such as in the following example:

#include <atomic>

int main()
{
  int a = 12;
  int& b = std::kill_dependency<int&>(a);
}

It is unclear what kind of dependency is killed here. This is presumably a core language problem, but could affect the possible resolutions of the problem.

[2014-11 Urbana]

Recommend using a revised example:

int lookup(class D* p) 
{
  class E* q = p->a.load(memory_order_consume);
  int y = std::kill_dependency(q->y);
}

[2015-02 Cologne]

Handed over to SG1.

Proposed resolution: