Doc number: N4388
Revises: N4372, N4209, N4057, N3973
Date: 2015-02-25
Project: Programming Language C++, Library Evolution Working Group
Reply-to: Jonathan Coe <jbcoe@me.com>
Robert Mill <rob.mill.uk@gmail.com>

A Proposal to Add a Const-Propagating Wrapper to the Standard Library

I. Introduction

We propose the introduction of a propagate_const wrapper class that propagates const-ness to pointer-like member variables.

II. Motivation

The behaviour of const member functions on objects with pointer-like data members is seen to be surprising by many experienced C++ developers. A const member function can call non-const functions on pointer-like data members and will do so by default without use of const_cast.

Example:

struct A
{
  void bar() const 
  { 
    std::cout << "bar (const)" << std::endl; 
  }
  
  void bar() 
  { 
    std::cout << "bar (non-const)" << std::endl; 
  }
};

struct B
{
  B() : m_ptrA(std::make_unique<A>()) {} 
  
  void foo() const 
  { 
    std::cout << "foo (const)" << std::endl;
    m_ptrA->bar(); 
  }           
  
  void foo() 
  { 
    std::cout << "foo (non-const)" << std::endl;
    m_ptrA->bar(); 
  }

  std::unique_ptr<A> m_ptrA;
};

int main()
{    
  B b;
  b.foo();
  
  const B const_b;
  const_b.foo();
}

Running this program gives the following output:

  foo (non-const)
  bar (non-const)
  foo (const)
  bar (non-const)

The behaviour above can be amended by re-writing void B::foo() const using const_cast to explicitly call the const member function of A. Such a change is unnatural and not common practice. We propose the introduction of a wrapper class which can be used on pointer-like member data to ensure propagation of const-ness.

Introducing propagate_const

The class propagate_const is designed to function as closely as possible to a traditional pointer or smart-pointer. Pointer-like member objects can be wrapped in a propagate_const object to ensure propagation of const-ness.

A const-propagating B would be written as

struct B
{
  B();              // unchanged
  
  void foo() const; // unchanged
  void foo();       // unchanged

  std::propagate_const<std::unique_ptr<A>> m_ptrA;
};

With an amended B, running the program from the earlier example will give the following output:

  foo (non-const)
  bar (non-const)
  foo (const)
  bar (const)

The pimpl idiom with propagate_const

The pimpl (pointer-to-implementation) idiom pushes implementation details of a class into a separate object, a pointer to which is stored in the original class [2].

class C
{
  void foo() const;
  void foo();
    
  std::unique_ptr<CImpl> m_pimpl;
};

void C::foo() const 
{ 
  m_pimpl->foo(); 
}

void C::foo() 
{ 
  m_pimpl->foo(); 
}

When using the pimpl idiom the compiler will not catch changes to member variables within const member functions. Member variables are kept in a separate object and the compiler only checks that the address of this object is unchanged. By introducing the pimpl idiom into a class to decouple interface and implementation, the author may have inadventantly lost compiler checks on const-correctness.

When the pimpl object is wrapped in propagate_const, const member functions will only be able to call const functions on the pimpl object and will be unable to modify (non-mutable) member variables of the pimpl object without explicit const_casts: const-correctness is restored. The class above would be modified as follows:

class C
{
  void foo() const;  // unchanged
  void foo();        // unchanged
  
  std::propagate_const<std::unique_ptr<CImpl>> m_pimpl;
};

Thread-safety and propagate_const

Herb Sutter introduced the appealing notion that const implies thread-safe [3]. Without propagate_const, changes outside a class with pointer-like members can render the const methods of that class non-thread-safe. This means that maintaining the rule const=>thread-safe requires a global review of the code base.

With only the const version of foo() the code below is thread-safe. Introduction of a non-const (and non-thread-safe) foo() into D renders E non-thread-safe.

struct D
{
  int foo() const { /* thread-safe */ }
  int foo() { /* non-thread-safe */ }
};

struct E
{
  E(D& pD) : m_pD{&pD} {}

  void operator() () const
  {
    m_pD->foo();
  }

  D* m_pD;
};

int main()
{
  D d;
  const E e1(d);
  const E e2(d);

  std::thread t1(e1);
  std::thread t2(e2);
  t1.join();
  t2.join();
}

One solution to the above is to forbid pointer-like member variables in classes if const=>thread-safe. This is undesirably restrictive. If instead all pointer-like member variables are decorated with propagate_const then the compiler will catch violations of const-ness that could render code non-thread-safe.

struct E
{
  E(D& pD);                 // unchanged

  void operator() () const; // unchanged

  std::propagate_const<D*> m_pD;
};

Introduction of propagate_const cannot automatically guarantee thread-safety but can allow const=>thread-safe to be locally verified during code review.

III. Impact On the Standard

This proposal is a pure library extension. It does not require changes to any standard classes, functions or headers.

IV. Design Decisions

Given absolute freedom we would propose changing the const keyword to propagate const-ness. That would be impractical, however, as it would break existing code and change behaviour in potentially undesirable ways. A second approach would be the introduction of a new keyword to modify const, for instance, deep const, which enforces const-propagation. Although this change would maintain backward-compatibility, it would require enhancements to the C++ compiler.

We suggest that the standard library supply a class that wraps member data where const-propagating behaviour is required. The propagate_const wrapper can be used much like the const keyword and will cause compilation failure wherever const-ness is violated. const-propagation can be introduced into existing code by decorating pointer-like members of a class with propagate_const.

The change required to introduce const-propagation to a class is simple and local enough to be enforced during code review and taught to C++ developers in the same way as smart-pointers are taught to ensure exception safety.

It is intended that propagate_const contain no member data besides the wrapped pointer. Inlining of function calls by the compiler will ensure that using propagate_const incurs no run-time cost.

Encapsulation vs inheritance

Inheritance from the wrapped pointer-like object (where it is a class type) was considered but ruled out. The purpose of this wrapper is to help the author ensure const-propagation; if propagate_const<T> were to inherit from T, then it would allow potentially non-const member functions of T to be called in a const context.

Construction and assignment

A propagate_const<T> should be move-constructable and move-assignable from a U or a propagate_const<U> where U is any type that T can be constructed or assigned from. There should be no additional cost of construction for a propagate_const<T> beyond that for construction of a T. The wrapped T should not be value-initialized as this would incur a cost for object pointer types. If value-initialization is desirable then it can be accomplished with another wrapper class like boost::value_initialized [4].

The constructors should be revised to use EXPLICIT from N4387 once the Library Fundamentals TS references C++17.

Non-copyable

A propagate_const<T> cannot be copied or assigned from a const propagate_const<T>&. This is to prevent unintentional removal of const-safety through copying. For instance:

struct G;
struct F
{
  void const_method() const; // calls only const methods of m_g;
  
  void non_const_method(); // calls methods of m_g
  
  propagate_const<G*> m_g;
};

void some_function(const F& f) // const-ref passed in, no resources should be accessed in a non-const manner
{
  F copy_f = f;
  f.non_const_method();
}

As propagate_const cannot be copied, the default copy and assignment operators will not be generated for F. This will prevent the code above from compiling.

Pointer-like functions

operator* and operator-> are defined to preserve const-propagation. When a const propagate_const<T> is used only const member functions of T can be used without explicit casts.

The wrapped pointer in propagate_const<T> is only required to be non-null, not dereferenceable, for operator* and operator-> as past-the-end-of-array pointers are not considered. Pointer arithemtic is not supported, this is consistent with existing practice for standard library smart pointers.

get

The get function returns the address of the object pointed to by the wrapped pointer. get is intended to be used to ensure const-propagation is preserved when using interfaces which require C-style pointers

operator value*

When T is an object pointer type operator value* exists and allows implicit conversion to a pointer. This avoids using get to access the pointer in contexts where it was unnecesary before addition of the propagate_const wrapper.

Equality, inequality and comparison

Free-standing equality, inequality and comparison operators are provided so that a propagate_const<T> can be used in any equality, inequality or comparison where a T could be used. const-propagation should not alter the result of any equality, inequality or comparison operation.

swap

swap will swap the underlying pointers.

get_underlying

get_underlying is a free-standing function which allows the underlying pointer to be accessed. The use of this function allows const-propagation to be dropped and is therefore discouraged. The function is named such that it will be easy to find in code review.

hash

The hash struct is specialized so that inclusion of propagate_const does not alter the result of hash evaluation.

V. Technical Specification

X.Y   Class template propagate_const [propagate_const]

X.Y.1  Class template propagate_const general [propagate_const.general]

propagate_const is a wrapper around a pointer-like object type T which treats the wrapped pointer as a pointer to const when the wrapper is accessed through a const access path.

X.Y.2  Header <propagate_const> synopsis [propagate_const.synopsis]

namespace std {
namespace experimental {
inline namespace fundamentals_v2 {
  template <class T> class propagate_const { 
  public:                                                                                          
    typedef remove_reference_t<decltype(*declval<T&>())> element_type;

    // [propagate_const.ctor], constructors
    constexpr propagate_const() = default;
    propagate_const(const propagate_const& p) = delete;
    constexpr propagate_const(propagate_const&& p) = default;
    template <class U> 
      see below constexpr propagate_const(propagate_const<U>&& pu);
    template <class U> 
      see below constexpr propagate_const(U&& u); 
    
    // [propagate_const.assignment], assignment
    propagate_const& operator=(const propagate_const& p) = delete; 
    constexpr propagate_const& operator=(propagate_const&& p) = default; 
    template <class U> 
      constexpr propagate_const& operator=(propagate_const<U>&& pu);
    template <class U> 
      constexpr propagate_const& operator=(U&& u); 
    
    // [propagate_const.const_observers], const observers
    explicit constexpr operator bool() const;
    constexpr const element_type* operator->() const;
    constexpr operator const element_type*() const; //Not always defined
    constexpr const element_type& operator*() const;
    constexpr const element_type* get() const;
    
    // [propagate_const.non_const_observers], non-const observers
    constexpr element_type* operator->();
    constexpr operator element_type*(); //Not always defined 
    constexpr element_type& operator*();
    constexpr element_type* get();

    // [propagate_const.modifiers], modifiers
    constexpr void swap(propagate_const& pt) noexcept(see below);
 
  private:
    T t_; //exposition only
  };
  
  // [propagate_const.relational], relational operators
  template <class T>
    constexpr bool operator==(const propagate_const<T>& pt, nullptr_t);
  template <class T>
    constexpr bool operator==(nullptr_t, const propagate_const<T>& pu);
  
  template <class T>
    constexpr bool operator!=(const propagate_const<T>& pt, nullptr_t);
  template <class T>
    constexpr bool operator!=(nullptr_t, const propagate_const<T>& pu);
  
  template <class T, class U>
    constexpr bool operator==(const propagate_const<T>& pt, const propagate_const<U>& pu);
  template <class T, class U>
    constexpr bool operator!=(const propagate_const<T>& pt, const propagate_const<U>& pu);
  template <class T, class U>
    constexpr bool operator<(const propagate_const<T>& pt, const propagate_const<U>& pu);
  template <class T, class U>
    constexpr bool operator>(const propagate_const<T>& pt, const propagate_const<U>& pu);
  template <class T, class U>
    constexpr bool operator<=(const propagate_const<T>& pt, const propagate_const<U>& pu);
  template <class T, class U>
    constexpr bool operator>=(const propagate_const<T>& pt, const propagate_const<U>& pu);

  template <class T, class U>
    constexpr bool operator==(const propagate_const<T>& pt, const U& u);
  template <class T, class U>
    constexpr bool operator!=(const propagate_const<T>& pt, const U& u);
  template <class T, class U>
    constexpr bool operator<(const propagate_const<T>& pt, const U& u);
  template <class T, class U>
    constexpr bool operator>(const propagate_const<T>& pt, const U& u);
  template <class T, class U>
    constexpr bool operator<=(const propagate_const<T>& pt, const U& u);
  template <class T, class U>
    constexpr bool operator>=(const propagate_const<T>& pt, const U& u);
  
  template <class T, class U>
    constexpr bool operator==(const T& t, const propagate_const<U>& pu);
  template <class T, class U>
    constexpr bool operator!=(const T& t, const propagate_const<U>& pu);
  template <class T, class U>
    constexpr bool operator<(const T& t, const propagate_const<U>& pu);
  template <class T, class U>
    constexpr bool operator>(const T& t, const propagate_const<U>& pu);
  template <class T, class U>
    constexpr bool operator<=(const T& t, const propagate_const<U>& pu);
  template <class T, class U>
    constexpr bool operator>=(const T& t, const propagate_const<U>& pu);
  
  // [propagate_const.algorithms], specialized algorithms
  template <class T>
    constexpr void swap(propagate_const<T>& pt, propagate_const<T>& pt2) noexcept(see below);

  // [propagate_const.underlying], underlying pointer access
  template <class T>
    constexpr const T& get_underlying(const propagate_const<T>& pt) noexcept;
  template <class T>
    constexpr T& get_underlying(propagate_const<T>& pt) noexcept;
} //  end namespace fundamentals_v2   
} //  end namespace experimental

  // [propagate_const.hash], hash support
  template <class T> struct hash;
  template <class T>
    struct hash<experimental::fundamentals_v2::propagate_const<T>>;
  
  // [propagate_const.comparison_function_objects], comparison function objects
  template <class T> struct equal_to;
  template <class T>
    struct equal_to<experimental::fundamentals_v2::propagate_const<T>>;
  template <class T> struct not_equal_to;
  template <class T>
    struct not_equal_to<experimental::fundamentals_v2::propagate_const<T>>;
  template <class T> struct less;
  template <class T>
    struct less<experimental::fundamentals_v2::propagate_const<T>>;
  template <class T> struct greater;
  template <class T>
    struct greater<experimental::fundamentals_v2::propagate_const<T>>;
  template <class T> struct less_equal;
  template <class T>
    struct less_equal<experimental::fundamentals_v2::propagate_const<T>>;
  template <class T> struct greater_equal;
  template <class T>
    struct greater_equal<experimental::fundamentals_v2::propagate_const<T>>;
} // end namespace std

X.Y.3  propagate_const requirements on T [propagate_const.requirements]

T shall be an object pointer type or a class type for which decltype(*declval<T&>()) is an lvalue reference; otherwise the program is ill-formed.

If T is an array type, reference type, pointer to function type or pointer to (possibly cv-qualified) void, then the program is ill-formed.

[Note: propagate_const<const int*> is well-formed end note]

X.Y.3.1  propagate_const requirements on class type T [propagate_const.class_type_requirements]

If T is class type then it shall satisfy the following requirements. In this sub-clause t denotes a non-const lvalue of type T, ct is a const T& bound to t, element_type denotes an object type.

T and const T shall be contextually convertible to bool.

If T is implicitly convertible to element_type*, (element_type*)t == t.get() shall be true.

If const T is implicitly convertible to const element_type*, (const element_type*)ct == ct.get() shall be true.

Table XX Requirements on class types T
Expression Return type Pre-conditions Operational
semantics
t.get() element_type*
ct.get() const element_type* or element_type* t.get() == ct.get().
*t element_type& t.get() != nullptr *t refers to the same object as *(t.get())
*ct const element_type& or element_type& ct.get() != nullptr *ct refers to the same object as *(ct.get())
t.operator->() element_type* t.get() != nullptr t.operator->() == t.get()
ct.operator->() const element_type* or element_type* ct.get() != nullptr ct.operator->() == ct.get()
(bool)t bool (bool)t is equivalent to t.get() != nullptr
(bool)ct bool (bool)ct is equivalent to ct.get() != nullptr

X.Y.4  propagate_const constructors [propagate_const.ctor]

[Note: The following constructors are conditionally specified as explicit. This is typically implemented by declaring two such constructors, of which at most one participates in overload resolution. end note]

template <class U> see below constexpr propagate_const(propagate_const<U>&& pu)
1 Remarks: This constructor shall not participate in overload resolution unless is_constructible_v<T, U&&>. The constructor is specified as explicit if and only if !is_convertible_v<U&&, T>.
2 Effects: Initializes t_ as if direct-non-list-initializing an object of type T with the expression std::move(pu.t_).

template <class U> see below constexpr propagate_const(U&& u)
3 Remarks: This constructor shall not participate in overload resolution unless is_constructible_v<T, U&&> and decay_t<U> is not a specialization of propagate_const. The constructor is specified as explicit if and only if !is_convertible_v<U&&, T>.
4 Effects: Initializes t_ as if direct-non-list-initializing an object of type T with the expression std::forward<U>(u).

X.Y.5  propagate_const assignment [propagate_const.assignment]

template <class U> constexpr propagate_const operator=(propagate_const<U>&& pu)
1 Remarks: This function shall not participate in overload resolution unless U is implicitly convertible to T.
2 Effects: t_ = std::move(pu.t_).
3 Returns: *this.

template <class U> constexpr propagate_const operator=(U&& u)
4 Remarks: This function shall not participate in overload resolution unless U is implicitly convertible to T and decay_t<U> is not a specialization of propagate_const.
5 Effects: t_ = std::forward<U>(u).
6 Returns: *this.

X.Y.6  propagate_const const observers [propagate_const.const_observers]

explicit constexpr operator bool() const
1 Returns:(bool)t_.

constexpr const element_type* operator->() const
2 Requires: get() != nullptr.
3 Returns: get().

constexpr operator const element_type*() const
4 Returns: get().
5 Remarks: This function shall not participate in overload resolution unless T is an object pointer type or has an implicit conversion to const element_type*.

constexpr const element_type& operator*() const
6 Requires: get() != nullptr.
7 Returns: *get().

constexpr const element_type* get() const
8 Returns: t_ if T is an object pointer type, otherwise t_.get().

X.Y.7  propagate_const non-const observers [propagate_const.non_const_observers]

constexpr element_type* operator->()
1 Requires: get() != nullptr.
2 Returns: get().

constexpr operator element_type*()
3 Returns: get().
4 Remarks: This function shall not participate in overload resolution unless T is an object pointer type or has an implicit conversion to element_type*.

constexpr element_type& operator*()
5 Requires: get() != nullptr.
6 Returns: *get().

constexpr element_type* get()
7 Returns: t_ if T is an object pointer type, otherwise t_.get().

X.Y.8  propagate_const modifiers [propagate_const.modifiers]

constexpr void swap(propagate_const& pt) noexcept(see below)

The constant-expression in the exception-specification is noexcept(swap(t_, pt.t_)).

1 Effects: swap(t_, pt.t_).

X.Y.9  propagate_const relational operators [propagate_const.relational]

template <class T> constexpr bool operator==(const propagate_const<T>& pt, nullptr_t)
1 Returns: pt.t_ == nullptr.
template <class T> constexpr bool operator==(nullptr_t, const propagate_const<U>& pt)
2 Returns: nullptr == pt.t_.
template <class T> constexpr bool operator!=(const propagate_const<T>& pt, nullptr_t)
3 Returns: pt.t_ != nullptr.
template <class T> constexpr bool operator!=(nullptr_t, const propagate_const<T>& pt)
4 Returns: nullptr != pt.t_.

template <class T, class U>
  constexpr bool operator==(const propagate_const<T>& pt, const propagate_const<U>& pu)

5 Returns: pt.t_ == pu.t_.
template <class T, class U>
  constexpr bool operator!=(const propagate_const<T>& pt, const propagate_const<U>& pu)

6 Returns: pt.t_ != pu.t_.
template <class T, class U>
  constexpr bool operator<(const propagate_const<T>& pt, const propagate_const<U>& pu)

7 Returns: pt.t_ < pu.t_.
template <class T, class U>
  constexpr bool operator>(const propagate_const<T>& pt, const propagate_const<U>& pu)

8 Returns: pt.t_ > pu.t_.
template <class T, class U>
  constexpr bool operator<=(const propagate_const<T>& pt, const propagate_const<U>& pu)

9 Returns: pt.t_ <= pu.t_.
template <class T, class U>
  constexpr bool operator>=(const propagate_const<T>& pt, const propagate_const<U>& pu)

10 Returns: pt.t_ >= pu.t_.

template <class T, class U> constexpr bool operator==(const propagate_const<T>& pt, const U& u)
11 Returns: pt.t_ == u.
template <class T, class U> constexpr bool operator!=(const propagate_const<T>& pt, const U& u)
12 Returns: pt.t_ != u.
template <class T, class U> constexpr bool operator<(const propagate_const<T>& pt, const U& u)
13 Returns: pt.t_ < u.
template <class T, class U> constexpr bool operator>(const propagate_const<T>& pt, const U& u)
14 Returns: pt.t_ > u.
template <class T, class U> constexpr bool operator<=(const propagate_const<T>& pt, const U& u)
15 Returns: pt.t_ <= u.
template <class T, class U> constexpr bool operator>=(const propagate_const<T>& pt, const U& u)
16 Returns: pt.t_ >= u.

template <class T, class U> constexpr bool operator==(const T& t, const propagate_const<U>& pu)
17 Returns: t == pu.t_.
template <class T, class U> constexpr bool operator!=(const T& t, const propagate_const<U>& pu)
18 Returns: t != pu.t_.
template <class T, class U> constexpr bool operator<(const T& t, const propagate_const<U>& pu)
19 Returns: t < pu.t_.
template <class T, class U> constexpr bool operator>(const T& t, const propagate_const<U>& pu)
20 Returns: t > pu.t_.
template <class T, class U> constexpr bool operator<=(const T& t, const propagate_const<U>& pu)
21 Returns: t <= pu.t_.
template <class T, class U> constexpr bool operator>=(const T& t, const propagate_const<U>& pu)
22 Returns: t >= pu.t_.

X.Y.10  propagate_const specialized algorithms [propagate_const.algorithms]

template <class T> constexpr void swap(propagate_const<T>& pt1, propagate_const<T>& pt2) noexcept(see below)

The constant-expression in the exception-specification is noexcept(swap(pt1.t_, pt2.t_)).

1 Effects: swap(pt1.t_, pt2.t_).

X.Y.11  propagate_const underlying pointer access [propagate_const.underlying]

Access to the underlying object pointer type is through free functions rather than member functions. These functions are intended to resemble cast operations to encourage caution when using them.

template <class T> constexpr const T& get_underlying(const propagate_const<T>& pt) noexcept
1 Returns: a reference to the underlying object pointer type.
template <class T> constexpr T& get_underlying(propagate_const<T>& pt) noexcept
2 Returns: a reference to the underlying object pointer type.

X.Y.12  propagate_const hash support [propagate_const.hash]

template <class T> struct hash<experimental::fundamentals_v2::propagate_const<T>>

For an object p of type propagate_const<T>, hash<experimental::fundamentals_v2::propagate_const<T>>()(p) shall evaluate to the same value as hash<T>()(p.t_).

1 Requires: The specialization hash<T> shall be well-formed and well-defined, and shall meet the requirements of class template hash.

X.Y.13  propagate_const comparison function objects [propagate_const.comparison_function_objects]

template <class T> struct equal_to<experimental::fundamentals_v2::propagate_const<T>>

For objects p, q of type propagate_const<T>, equal_to<experimental::fundamentals_v2::propagate_const<T>>()(p, q) shall evaluate to the same value as equal_to<T>()(p.t_, q.t_).

1 Requires: The specialization equal_to<T> shall be well-formed and well-defined.
template <class T> struct not_equal_to<experimental::fundamentals_v2::propagate_const<T>>

For objects p, q of type propagate_const<T>, not_equal_to<experimental::fundamentals_v2::propagate_const<T>>()(p, q) shall evaluate to the same value as not_equal_to<T>()(p.t_, q.t_).

1 Requires: The specialization not_equal_to<T> shall be well-formed and well-defined.
template <class T> struct less<experimental::fundamentals_v2::propagate_const<T>>

For objects p, q of type propagate_const<T>, less<experimental::fundamentals_v2::propagate_const<T>>()(p, q) shall evaluate to the same value as less<T>()(p.t_, q.t_).

1 Requires: The specialization less<T> shall be well-formed and well-defined.
template <class T> struct greater<experimental::fundamentals_v2::propagate_const<T>>

For objects p, q of type propagate_const<T>, greater<experimental::fundamentals_v2::propagate_const<T>>()(p, q) shall evaluate to the same value as greater<T>()(p.t_, q.t_).

1 Requires: The specialization greater<T> shall be well-formed and well-defined.
template <class T> struct less_equal<experimental::fundamentals_v2::propagate_const<T>>

For objects p, q of type propagate_const<T>, less_equal<experimental::fundamentals_v2::propagate_const<T>>()(p, q) shall evaluate to the same value as less_equal<T>()(p.t_, q.t_).

1 Requires: The specialization less_equal<T> shall be well-formed and well-defined.
template <class T> struct greater_equal<experimental::fundamentals_v2::propagate_const<T>>

For objects p, q of type propagate_const<T>, greater_equal<experimental::fundamentals_v2::propagate_const<T>>()(p, q) shall evaluate to the same value as greater_equal<T>()(p.t_, q.t_).

1 Requires: The specialization greater_equal<T> shall be well-formed and well-defined.

VI. Acknowledgements

Thanks to Walter Brown, Kevin Channon, Daniel Krugler, Stephan T. Lavavej, Nick Maclaren, Roger Orr, Geoffrey Romer, Ville Voutilainen, Jonathan Wakely, David Ward, the staff of the Creative Assembly and others for helpful discussion.

VII. Revisions

This paper revises N4372

N4372 revises N4209

N4209 revises N4057

N4057 revises N3973

VIII. References