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

Similar documents
A Proposal to Add a Logical Const Wrapper to the Standard Library Technical Report

C++ Coding Standards. 101 Rules, Guidelines, and Best Practices. Herb Sutter Andrei Alexandrescu. Boston. 'Y.'YAddison-Wesley

A polymorphic value-type for C++

September 10,

A polymorphic value-type for C++

6.S096 Lecture 4 Style and Structure

Explicit Conversion Operator Draft Working Paper

C The new standard

A polymorphic value type for C++

Evaluation Issues in Generic Programming with Inheritance and Templates in C++

C++ (Non for C Programmer) (BT307) 40 Hours

A Generic Non-intrusive Smart Pointer Implementation

Type Erasure. Nevin :-) Liber Friday, October 29, Chicago C/C++ Users Group Nevin J. Liber All Rights Reserved.

Overview of C++: Part 1

Debug C++ Without Running. Anastasia Kazakova

Absolute C++ Walter Savitch

Object-Oriented Programming for Scientific Computing

use static size for this buffer

C++ Modern and Lucid C++ for Professional Programmers

Advanced C++ Topics. Alexander Warg, 2017

COMP6771 Advanced C++ Programming

A jthread automatically signals an interrupt at the end of its lifetime to the started thread (if still joinable) and joins:

You should never see the following pattern (in the sense of textual pattern) in your code:

Exception Safe Coding

Source-Code Information Capture

Introduction to C++ Professor Hugh C. Lauer CS-2303, System Programming Concepts

Operator overloading. Conversions. friend. inline

Instantiation of Template class

Tokens, Expressions and Control Structures

COMP6771 Advanced C++ Programming

Chapter 15 - C++ As A "Better C"

Before we dive in. Preprocessing Compilation Linkage

Implicit Evaluation of auto Variables and Arguments

COMP6771 Advanced C++ Programming

Basic Types, Variables, Literals, Constants

Lecture 2 Polymorphism, Traits, Policies, and Inheritance

Midterm Review. PIC 10B Spring 2018

Object-Oriented Programming for Scientific Computing

Implicit Evaluation of auto Variables and Arguments

More Improvements to std::future<t> - Revision 1

Fixing Atomic Initialization, Rev1

Fast Introduction to Object Oriented Programming and C++

Operator Overloading. Gary Powell, Doug Gregor, Jaakko Jȧ. rvi

A Proposal for the World s Dumbest Smart Pointer

Let return Be Direct and explicit

Simplifying C++0x Concepts

CS

memory_resource_ptr: A Limited Smart Pointer for memory_resource Correctness

An Introduction to C++

Modernizing legacy C++ code

Qualified std::function signatures

Introduction to C++ Systems Programming

Concurrent Programming in the D Programming Language. by Walter Bright Digital Mars

Exceptions. CandC++ 7. Exceptions Templates. Throwing exceptions. Conveying information

(heavily based on last year s notes (Andrew Moore) with thanks to Alastair R. Beresford. 7. Exceptions Templates 2/1. Throwing exceptions 14 }

Introduction to C++ Introduction. Structure of a C++ Program. Structure of a C++ Program. C++ widely-used general-purpose programming language

STUDY NOTES UNIT 1 - INTRODUCTION TO OBJECT ORIENTED PROGRAMMING

From Java to C++ From Java to C++ CSE250 Lecture Notes Weeks 1 2, part of 3. Kenneth W. Regan University at Buffalo (SUNY) September 10, 2009

Structured bindings with polymorphic lambas

III. Classes (Chap. 3)

A Proposal for the World s Dumbest Smart Pointer, v2

Rvalue References & Move Semantics

C++17 and Beyond. Mark Isaacson

Introduction to C++ with content from

6 Architecture of C++ programs

Part III. Advanced C ++

Digging into the GAT API

C++11 and Compiler Update

Introduction to C++ Introduction to C++ 1

A Cooperatively Interruptible Joining Thread, Rev 4 New in R4

Database Systems on Modern CPU Architectures

Tutorial 7. Y. Bernat. Object Oriented Programming 2, Spring Y. Bernat Tutorial 7

Short Notes of CS201

Object-Oriented Programming for Scientific Computing

Intro to OOP Visibility/protection levels and constructors Friend, convert constructor, destructor Operator overloading a<=b a.

CS201 - Introduction to Programming Glossary By

Auxiliary class interfaces

G52CPP C++ Programming Lecture 20

Abstraction and Encapsulation. Benefits of Abstraction & Encapsulation. Concrete Types. Using Typedefs to streamline classes.

Object-Oriented Programming for Scientific Computing

CS 376b Computer Vision

0-overhead-principle violations in exception handling - part 2

Red Code / Green Code: Generalizing const

Adding a subsection for concurrent random number generation in C++17

Appendix. Grammar. A.1 Introduction. A.2 Keywords. There is no worse danger for a teacher than to teach words instead of things.

CS11 Advanced C++ Fall Lecture 7

Advanced C++ 4/13/2017. The user. Types of users. Const correctness. Const declaration. This pointer and const.

Variant: a typesafe union. ISO/IEC JTC1 SC22 WG21 N4218

A Whirlwind Tour of C++

THE PROGRAMMING RESEARCH GROUP

Generalized pointer casts

Exception Namespaces C Interoperability Templates. More C++ David Chisnall. March 17, 2011

Operators. The Arrow Operator. The sizeof Operator

Friendship in Service of Testing

A Cooperatively Interruptible Joining Thread, Rev 2

Object-Oriented Programming

Overload Resolution. Ansel Sermersheim & Barbara Geller Amsterdam C++ Group March 2019

Making New Pseudo-Languages with C++

Programming 2. Object Oriented Programming. Daniel POP

Hazard Pointers. Safe Resource Reclamation for Optimistic Concurrency

Transcription:

Doc number: N4057 Revises: N3973 Date: 2014-07-02 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 consvcast. 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; ; 1

int main() B b; b.foo(); const B consvb; consvb.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 consvcast 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; 2

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 consvcasts: 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(); 3

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. 4

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 constructable and 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 raw pointers. If value-initialization is desirable then it can be accomplished with another wrapper class like boost::value_initialized [4]. 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. 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 raw C-style pointers operator value* When T is a raw pointer operator value* exists and allows implicit conversion to a raw pointer. This avoids using get to access the raw 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 The swap function should not add or remove const-ness but should not unduly restrict the types with which propagate_const<t> can be swapped. If T and U can be swapped then const-propagating T and U can be swapped. 5

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. noexcept We have omitted noexcept specifications until we have stronger guidelines from the Library Evolution Working group. The noexcept status of all functions will depend on the noexcept status of the corresponding functions on the underlying pointer. V. Expository Implementation A sample form of std::propagate_const is given below and makes use of an overloaded templated helper function: underlying_pointer. template <typename T> class propagate_const typedef decltype(*std::declval<t>()) reference_type; public: using value_type = std::enable_if_t< std::is_lvalue_reference<reference_type>::value, typename std::remove_reference<reference_type>::type>::type; ~propagate_const() = default; propagate_const() = default; propagate_const(u&& u) : tstd::forward<u>(u) propagate_const<t>& operator = (U&& u) t = std::forward<u>(u); return *this; propagate_const(const propagate_const<u>& pu) : tpu.t 6

propagate_const(propagate_const<u>&& pu) : tstd::move(pu.t) propagate_const<t>& operator = (const propagate_const<u>& pt) t = pt.t; return *this; propagate_const<t>& operator = (propagate_const<u>&& pt) t = std::move(pt.t); return *this; value_type* operator->() const value_type* operator->() const value_type* get() const value_type* get() const template <typename T_=T, typename V = std::enable_if_t<std::is_pointer<t_>::value>> operator value_type*() template <typename T_=T, typename V = std::enable_if_t<std::is_pointer<t_>::value>> operator const value_type*() const value_type& operator*() return *t; 7

const value_type& operator*() const return *t; explicit operator bool () const return static_cast<bool>(t); private: T t; template<typename U> static value_type* underlying_pointer(u* p) return p; template<typename U> static value_type* underlying_pointer(u& p) return p.get(); template<typename U> static const value_type* underlying_pointer(const U* p) return p; template<typename U> static const value_type* underlying_pointer(const U& p) return p.get(); ; bool operator == (const propagate_const<t>& pt, const propagate_const<u>& pu) return pt.t == pu.t; bool operator!= (const propagate_const<t>& pt, const propagate_const<u>& pu) return pt.t!= pu.t; bool operator < (const propagate_const<t>& pt, const propagate_const<u>& pu) return pt.t < pu.t; bool operator > (const propagate_const<t>& pt, const propagate_const<u>& pu) return pt.t > pu.t; 8

bool operator <= (const propagate_const<t>& pt, const propagate_const<u>& pu) return pt.t <= pu.t; bool operator >= (const propagate_const<t>& pt, const propagate_const<u>& pu) return pt.t >= pu.t; void swap (propagate_const<t>& pt1, propagate_const<u>& pt2) swap(pt1.t, pt2.t); template <typename T> const T& get_underlying(const propagate_const<t>& pt) return pt.t; template <typename T> T& get_underlying(propagate_const<t>& pt) return pt.t; template <typename T> struct hash<propagate_const<t>> : std::hash<t> size_t operator()(const propagate_const<t>& p) const return operator()(get_underlying(p)); ; VI Acknowledgements Thanks to Walter Brown, Kevin Channon, Nick Maclaren, Roger Orr, Ville Voutilainen, Jonathan Wakely, David Ward and others for helpful discussion. VII References [1] Bjarne Stroustrup, The C++ Programming Language, 4th edition, 2013, Addison Wesley ISBN-10: 0321563840 p464 [2] Martin Reddy, API design for C++, 2011, Elsevier ISBN-10: 0123850037, Section 3.1 [3] Herb Sutter, C++ and Beyond 2012: Herb Sutter - You don't know [blank] and [blank] [4] boost value_initialized 9