Lecture Slides for Programming in C++
[The C++ Language, Libraries, Tools, and Other Topics]
(Version 2021-04-01)
With Coverage of C++20 Standard
Michael D. Adams
Department of Electrical and Computer Engineering
University of Victoria
Victoria, British Columbia, Canada
To obtain the ⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
most recent version of these lecture slides (with functional hyperlinks) or for additional
information and resources related to these slides (including video lectures and errata), please visit:
................
http://www.ece.uvic.ca/~mdadams/cppbook
If you like these lecture slides, please consider posting a review of them at:
https://play.google.com/store/search?q=ISBN:9780987919748 or
http://books.google.com/books?vid=ISBN9780987919748
youtube.com/iamcanadian1867 github.com/mdadams @mdadams16
The author has taken care in the preparation of this document, but makes no expressed or implied warranty of any kind and assumes no
responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use
of the information or programs contained herein.
Copyright © 2015, 2016, 2017, 2018, 2019, 2020, 2021 Michael D. Adams
This document is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-NC-ND 3.0) License. A copy
of this license can be found on page iii of this document. For a simple explanation of the rights granted by this license, see:
http://creativecommons.org/licenses/by-nc-nd/3.0/
UNIX and X Window System are registered trademarks of The Open Group. Windows is a registered trademark of Microsoft Corporation.
Fedora is a registered trademark of Red Hat, Inc. Ubuntu is a registered trademark of Canonical Ltd. MATLAB is a registered trademark of The
MathWorks, Inc. OpenGL is a registered trademark of Hewlett Packard Enterprise. The YouTube logo is a registered trademark of Google, Inc.
The GitHub logo is a registered trademark of GitHub, Inc. The Twitter logo is a registered trademark of Twitter, Inc.
This document was typeset with LATEX.
ISBN 978-0-9879197-4-8 (PDF)
License I
Creative Commons Legal Code
Attribution-NonCommercial-NoDerivs 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and
other pre-existing works, such as a translation, adaptation,
derivative work, arrangement of music or other alterations of a
literary or artistic work, or phonogram or performance and includes
cinematographic adaptations or any other form in which the Work may be
recast, transformed, or adapted including in any form recognizably
derived from the original, except that a work that constitutes a
Collection will not be considered an Adaptation for the purpose of
this License. For the avoidance of doubt, where the Work is a musical
work, performance or phonogram, the synchronization of the Work in
timed-relation with a moving image ("synching") will be considered an
Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as
encyclopedias and anthologies, or performances, phonograms or
broadcasts, or other works or subject matter other than works listed
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 iii
License II
in Section 1(f) below, which, by reason of the selection and
arrangement of their contents, constitute intellectual creations, in
which the Work is included in its entirety in unmodified form along
with one or more other contributions, each constituting separate and
independent works in themselves, which together are assembled into a
collective whole. A work that constitutes a Collection will not be
considered an Adaptation (as defined above) for the purposes of this
License.
c. "Distribute" means to make available to the public the original and
copies of the Work through sale or other transfer of ownership.
d. "Licensor" means the individual, individuals, entity or entities that
offer(s) the Work under the terms of this License.
e. "Original Author" means, in the case of a literary or artistic work,
the individual, individuals, entity or entities who created the Work
or if no individual or entity can be identified, the publisher; and in
addition (i) in the case of a performance the actors, singers,
musicians, dancers, and other persons who act, sing, deliver, declaim,
play in, interpret or otherwise perform literary or artistic works or
expressions of folklore; (ii) in the case of a phonogram the producer
being the person or legal entity who first fixes the sounds of a
performance or other sounds; and, (iii) in the case of broadcasts, the
organization that transmits the broadcast.
f. "Work" means the literary and/or artistic work offered under the terms
of this License including without limitation any production in the
literary, scientific and artistic domain, whatever may be the mode or
form of its expression including digital form, such as a book,
pamphlet and other writing; a lecture, address, sermon or other work
of the same nature; a dramatic or dramatico-musical work; a
choreographic work or entertainment in dumb show; a musical
composition with or without words; a cinematographic work to which are
assimilated works expressed by a process analogous to cinematography;
a work of drawing, painting, architecture, sculpture, engraving or
lithography; a photographic work to which are assimilated works
expressed by a process analogous to photography; a work of applied
art; an illustration, map, plan, sketch or three-dimensional work
relative to geography, topography, architecture or science; a
performance; a broadcast; a phonogram; a compilation of data to the
extent it is protected as a copyrightable work; or a work performed by
a variety or circus performer to the extent it is not otherwise
considered a literary or artistic work.
g. "You" means an individual or entity exercising rights under this
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 iv
License III
License who has not previously violated the terms of this License with
respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous
violation.
h. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or
process, including by wire or wireless means or public digital
performances; to make available to the public Works in such a way that
members of the public may access these Works from a place and at a
place individually chosen by them; to perform the Work to the public
by any means or process and the communication to the public of the
performances of the Work, including by public digital performance; to
broadcast and rebroadcast the Work by any means including signs,
sounds or images.
i. "Reproduce" means to make copies of the Work by any means including
without limitation by sound or visual recordings and the right of
fixation and reproducing fixations of the Work, including storage of a
protected performance or phonogram in digital form or other electronic
medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
limit, or restrict any uses free from copyright or rights arising from
limitations or exceptions that are provided for in connection with the
copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more
Collections, and to Reproduce the Work as incorporated in the
Collections; and,
b. to Distribute and Publicly Perform the Work including as incorporated
in Collections.
The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights in
other media and formats, but otherwise you have no rights to make
Adaptations. Subject to 8(f), all rights not expressly granted by Licensor
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 v
License IV
are hereby reserved, including but not limited to the rights set forth in
Section 4(d).
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms
of this License. You must include a copy of, or the Uniform Resource
Identifier (URI) for, this License with every copy of the Work You
Distribute or Publicly Perform. You may not offer or impose any terms
on the Work that restrict the terms of this License or the ability of
the recipient of the Work to exercise the rights granted to that
recipient under the terms of the License. You may not sublicense the
Work. You must keep intact all notices that refer to this License and
to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Work, You may not impose any effective technological
measures on the Work that restrict the ability of a recipient of the
Work from You to exercise the rights granted to that recipient under
the terms of the License. This Section 4(a) applies to the Work as
incorporated in a Collection, but this does not require the Collection
apart from the Work itself to be made subject to the terms of this
License. If You create a Collection, upon notice from any Licensor You
must, to the extent practicable, remove from the Collection any credit
as required by Section 4(c), as requested.
b. You may not exercise any of the rights granted to You in Section 3
above in any manner that is primarily intended for or directed toward
commercial advantage or private monetary compensation. The exchange of
the Work for other copyrighted works by means of digital file-sharing
or otherwise shall not be considered to be intended for or directed
toward commercial advantage or private monetary compensation, provided
there is no payment of any monetary compensation in connection with
the exchange of copyrighted works.
c. If You Distribute, or Publicly Perform the Work or Collections, You
must, unless a request has been made pursuant to Section 4(a), keep
intact all copyright notices for the Work and provide, reasonable to
the medium or means You are utilizing: (i) the name of the Original
Author (or pseudonym, if applicable) if supplied, and/or if the
Original Author and/or Licensor designate another party or parties
(e.g., a sponsor institute, publishing entity, journal) for
attribution ("Attribution Parties") in Licensor’s copyright notice,
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 vi
License V
terms of service or by other reasonable means, the name of such party
or parties; (ii) the title of the Work if supplied; (iii) to the
extent reasonably practicable, the URI, if any, that Licensor
specifies to be associated with the Work, unless such URI does not
refer to the copyright notice or licensing information for the Work.
The credit required by this Section 4(c) may be implemented in any
reasonable manner; provided, however, that in the case of a
Collection, at a minimum such credit will appear, if a credit for all
contributing authors of Collection appears, then as part of these
credits and in a manner at least as prominent as the credits for the
other contributing authors. For the avoidance of doubt, You may only
use the credit required by this Section for the purpose of attribution
in the manner set out above and, by exercising Your rights under this
License, You may not implicitly or explicitly assert or imply any
connection with, sponsorship or endorsement by the Original Author,
Licensor and/or Attribution Parties, as appropriate, of You or Your
use of the Work, without the separate, express prior written
permission of the Original Author, Licensor and/or Attribution
Parties.
d. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme cannot be waived, the Licensor
reserves the exclusive right to collect such royalties for any
exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme can be waived, the Licensor reserves
the exclusive right to collect such royalties for any exercise by
You of the rights granted under this License if Your exercise of
such rights is for a purpose or use which is otherwise than
noncommercial as permitted under Section 4(b) and otherwise waives
the right to collect royalties through any statutory or compulsory
licensing scheme; and,
iii. Voluntary License Schemes. The Licensor reserves the right to
collect royalties, whether individually or, in the event that the
Licensor is a member of a collecting society that administers
voluntary licensing schemes, via that society, from any exercise
by You of the rights granted under this License that is for a
purpose or use which is otherwise than noncommercial as permitted
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 vii
License VI
under Section 4(b).
e. Except as otherwise agreed in writing by the Licensor or as may be
otherwise permitted by applicable law, if You Reproduce, Distribute or
Publicly Perform the Work either by itself or as part of any
Collections, You must not distort, mutilate, modify or take other
derogatory action in relation to the Work which would be prejudicial
to the Original Author’s honor or reputation.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED BY THE PARTIES IN WRITING, LICENSOR
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate
automatically upon any breach by You of the terms of this License.
Individuals or entities who have received Collections from You under
this License, however, will not have their licenses terminated
provided such individuals or entities remain in full compliance with
those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any
termination of this License.
b. Subject to the above terms and conditions, the license granted here is
perpetual (for the duration of the applicable copyright in the Work).
Notwithstanding the above, Licensor reserves the right to release the
Work under different license terms or to stop distributing the Work at
any time; provided, however that any such election will not serve to
withdraw this License (or any other license that has been, or is
required to be, granted under the terms of this License), and this
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 viii
License VII
License will continue in full force and effect unless terminated as
stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection,
the Licensor offers to the recipient a license to the Work on the same
terms and conditions as the license granted to You under this License.
b. If any provision of this License is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this License, and without further action
by the parties to this agreement, such provision shall be reformed to
the minimum extent necessary to make such provision valid and
enforceable.
c. No term or provision of this License shall be deemed waived and no
breach consented to unless such waiver or consent shall be in writing
and signed by the party to be charged with such waiver or consent.
d. This License constitutes the entire agreement between the parties with
respect to the Work licensed here. There are no understandings,
agreements or representations with respect to the Work not specified
here. Licensor shall not be bound by any additional provisions that
may appear in any communication from You. This License may not be
modified without the mutual written agreement of the Licensor and You.
e. The rights granted under, and the subject matter referenced, in this
License were drafted utilizing the terminology of the Berne Convention
for the Protection of Literary and Artistic Works (as amended on
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
and the Universal Copyright Convention (as revised on July 24, 1971).
These rights and subject matter take effect in the relevant
jurisdiction in which the License terms are sought to be enforced
according to the corresponding provisions of the implementation of
those treaty provisions in the applicable national law. If the
standard suite of rights granted under applicable copyright law
includes additional rights not granted under this License, such
additional rights are deemed to be included in the License; this
License is not intended to restrict the license of any rights under
applicable law.
Creative Commons Notice
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 ix
License VIII
Creative Commons is not a party to this License, and makes no warranty
whatsoever in connection with the Work. Creative Commons will not be
liable to You or any party on any legal theory for any damages
whatsoever, including without limitation any general, special,
incidental or consequential damages arising in connection to this
license. Notwithstanding the foregoing two (2) sentences, if Creative
Commons has expressly identified itself as the Licensor hereunder, it
shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the
Work is licensed under the CCPL, Creative Commons does not authorize
the use by either party of the trademark "Creative Commons" or any
related trademark or logo of Creative Commons without the prior
written consent of Creative Commons. Any permitted use will be in
compliance with Creative Commons’ then-current trademark usage
guidelines, as may be published on its website or otherwise made
available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of this License.
Creative Commons may be contacted at http://creativecommons.org/.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 x
Other Textbooks and Lecture Slides by the Author I
1 M. D. Adams, Exercises for Programming in C++ (Version 2021-04-01),
University of Victoria, Victoria, BC, Canada, Apr. 2021, xxii + 136 pages,
ISBN 978-0-9879197-5-5 (PDF). Available from Google Books, Google
Play Books, and author’s web site
http://www.ece.uvic.ca/~mdadams/cppbook.
2 M. D. Adams, Signals and Systems, Edition 3.0, University of Victoria,
Victoria, BC, Canada, Dec. 2020, xliv + 680 pages, ISBN
978-1-55058-673-2 (print), ISBN 978-1-55058-674-9 (PDF). Available
from Google Books, Google Play Books, University of Victoria Bookstore,
and author’s web site
http://www.ece.uvic.ca/~mdadams/sigsysbook.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xi
Other Textbooks and Lecture Slides by the Author II
3 M. D. Adams, Lecture Slides for Signals and Systems, Edition 3.0,
University of Victoria, Victoria, BC, Canada, Dec. 2020, xvi + 625 slides,
ISBN 978-1-55058-677-0 (print), ISBN 978-1-55058-678-7 (PDF).
Available from Google Books, Google Play Books, University of Victoria
Bookstore, and author’s web site
http://www.ece.uvic.ca/~mdadams/sigsysbook.
4 M. D. Adams, Multiresolution Signal and Geometry Processing: Filter
Banks, Wavelets, and Subdivision (Version 2013-09-26), University of
Victoria, Victoria, BC, Canada, Sept. 2013, xxxviii + 538 pages, ISBN
978-1-55058-507-0 (print), ISBN 978-1-55058-508-7 (PDF). Available
from Google Books, Google Play Books, University of Victoria Bookstore,
and author’s web site
http://www.ece.uvic.ca/~mdadams/waveletbook.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xii
Other Textbooks and Lecture Slides by the Author III
5 M. D. Adams, Lecture Slides for Multiresolution Signal and Geometry
Processing (Version 2015-02-03), University of Victoria, Victoria, BC,
Canada, Feb. 2015, xi + 587 slides, ISBN 978-1-55058-535-3 (print),
ISBN 978-1-55058-536-0 (PDF). Available from Google Books, Google
Play Books, University of Victoria Bookstore, and author’s web site
http://www.ece.uvic.ca/~mdadams/waveletbook.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xiii
Part 0
Preface
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xiv
About These Lecture Slides
■ This document constitutes a detailed set of lecture slides on the C++
programming language and is current with the C++20 standard. ⁓⁓⁓⁓⁓⁓⁓
[C++20 §(full)]
■ Many aspects of the C++ language are covered from introductory to more
advanced.
■ Some aspects of the C++ standard library are also introduced.
■ In addition, various general programming-related topics are considered.
■ These slides are intended to be used in conjunction with the following
book:
2 M. D. Adams, Exercises for Programming in C++ (Version 2021-04-01),
University of Victoria, Victoria, BC, Canada, Apr. 2021, xxii + 136 pages,
ISBN 978-0-9879197-5-5 (PDF). Available from Google Books, Google Play
Books, and author’s web site
http://www.ece.uvic.ca/~mdadams/cppbook.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xv
Acknowledgments
■ The author would like to thank Robert Leahy for reviewing various drafts of
many of these slides and providing many useful comments that allowed
the quality of these materials to be improved significantly.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xvi
Disclaimer
■ Many code examples are included throughout these slides.
■ Often, in order to make an example short enough to fit on a slide,
compromises had to be made in terms of good programming style.
■ These deviations from good style include (but are not limited to) such
things as:
1 frequently formatting source code in unusual ways to conserve vertical
space in listings;
2 not fully documenting source code with comments;
3 using short meaningless identifier names;
4 omitting include guards from headers; and
5 engaging in various other evil behavior such as: using many global
variables and employing constructs like “using namespace std;”.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xvii
Typesetting Conventions
■ In a definition, the term being defined is often typeset like this.
■ To emphasize particular text, the text is typeset like this.
■ To show that particular text is associated with a hyperlink to an internal
target, the text is typeset .........
like this.
■ To show that particular text is associated with a hyperlink to an external
document, the text is typeset like this.
⁓⁓⁓⁓⁓⁓
■ URLs are typeset like http://www.ece.uvic.ca/~mdadams.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xviii
Companion Web Site
■ The author of the lecture slides maintains a companion web site for the
slides.
■ The most recent version of the slides can be downloaded from this site.
■ Additional information related to the slides is also available from this site,
including:
2 errata for the slides; and
2 information on the companion web site, companion Git repository, and
companion YouTube channel for the slides.
■ The URL of this web site is:
2 http://www.ece.uvic.ca/~mdadams/cppbook
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xix
Video Lectures
■ The author has prepared video lectures for some of the material covered
in these slides and the associated book.
■ All of the videos are hosted by YouTube and available through the author’s
YouTube channel:
2 https://www.youtube.com/iamcanadian1867
■ The most up-to-date information about this video-lecture content can be
found at:
2 https://www.ece.uvic.ca/~mdadams/cppbook/#video_lectures
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xx
Companion Git Repository
■ These lecture slides have a companion Git repository.
■ Numerous code examples and exercises are available from this repository.
■ This repository is hosted by GitHub.
■ The URL of the main repository page on GitHub is:
2 https://github.com/mdadams/cppbook_companion
■ The URL of the actual repository itself is:
2 https://github.com/mdadams/cppbook_companion.git
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xxi
Software Development Environment (SDE)
■ The Software Development Environment (SDE) is a collection of tools that
can be used to provide a basic up-to-date environment for C++ code
development.
■ The SDE should work with most Linux distributions, provided that the
necessary software dependencies are installed.
■ Amongst other things, the SDE software provides a collection of scripts for
installing packages like:
2 Boost, Catch2, CGAL, Clang, CMake, GCC, Gcovr, GDB, GSL, Lcov,
Libcxx, TeX Live, Vim, Vim LSP, and YCM
■ The SDE software has a Git repository hosted by GitHub.
■ The URL of the main repository page on GitHub is:
2 https://github.com/mdadams/sde
■ The URL of the actual repository itself is:
2 https://github.com/mdadams/sde.git
■ For more information about the SDE, refer to the main repository page on
GitHub.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xxii
Virtual Machine (VM) Disk Images for the SDE
■ The author has prepared a number of virtual-machine (VM) disk images
that contain various versions of the Software Development Environment
(SDE) mentioned on the previous slide.
■ These disk images are likely to be helpful to individuals who need a
usable software development environment for C++ programming.
■ The VM disk images are available in several formats and should be usable
with most popular VM hypervisor software (such as VirtualBox, VMWare
Workstation Player, and GNOME Boxes).
■ The VM disk images can be obtained from:
2 https://ece.uvic.ca/~mdadams/cppbook/#vm_disk_images
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xxiii
Part 1
Software
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1
Why Is Software Important?
■ almost all electronic devices run some software
■ automobile engine control system, implantable medical devices, remote
controls, office machines (e.g., photocopiers), appliances (e.g.,
televisions, refrigerators, washers/dryers, dishwashers, air conditioner),
power tools, toys, mobile phones, media players, computers, printers,
photocopies, disk drives, scanners, webcams, MRI machines
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2
Why Software-Based Solutions?
■ more cost effective to implement functionality in software than hardware
■ software bugs easy to fix, give customer new software upgrade
■ hardware bugs extremely costly to repair, customer sends in old device
and manufacturer sends replacement
■ systems increasingly complex, bugs unavoidable
■ allows new features to be added later
■ implement only absolute minimal functionality in hardware, do the rest in
software
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 3
Software-Related Jobs
■ many more software jobs than hardware jobs
■ relatively small team of hardware designers produce platform like iPhone
■ thousands of companies develop applications for platform
■ only implement directly in hardware when absolutely necessary (e.g., for
performance reasons)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 4
C
■ created by Dennis Ritchie, AT&T Bell Labs in 1970s
■ international standard ISO/IEC 9899
■ available on wide range of platforms, from microcontrollers to
supercomputers; very few platforms for which C compiler not available
■ procedural, provides language constructs that map efficiently to machine
instructions
■ does not directly support object-oriented or generic programming
■ application domains: system software, device drivers, embedded
applications, application software
■ greatly influenced development of C++
■ when something lasts in computer industry for more than 40 years
(outliving its creator), must be good
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 5
C++
■ created by Bjarne Stroustrup, Bell Labs
■ originally C with Classes, renamed as C++ in 1983
■ international standard ISO/IEC 14882
■ procedural
■ loosely speaking is superset of C
■ directly supports object-oriented and generic programming
■ maintains efficiency of C
■ application domains: systems software, application software, device
drivers, embedded software, high-performance server and client
applications, entertainment software such as video games, native code for
Android applications
■ greatly influenced development of C# and Java
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 6
Java
■ developed in 1990s by James Gosling at Sun Microsystems (later bought
by Oracle Corporation)
■ de facto standard but not international standard
■ usually less efficient than C and C++
■ simplified memory management (with garbage collection)
■ direct support for object-oriented programming
■ application domains: web applications, Android applications
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 7
Fortran
■ designed by John Backus, IBM, in 1950s
■ international standard ISO/IEC 1539-1
■ application domain: scientific and engineering applications, intensive
supercomputing tasks such as weather and climate modelling, finite
element analysis, computational fluid dynamics, computational physics,
computational chemistry
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 8
C#
■ developed by Microsoft, team led by Anders Hejlsberg
■ ECMA-334 and ISO/IEC 23270
■ most recent language specifications not standardized by ECMA or
ISO/IEC
■ intellectual property concerns over Microsoft patents
■ object oriented
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 9
Objective C
■ developed by Tom Love and Brad Cox of Stepstone (later bought by NeXT
and subsequently Apple)
■ used primarily on Apple Mac OS X and iOS
■ strict superset of C
■ no official standard that describes Objective C
■ authoritative manual on Objective-C 2.0 available from Apple
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 10
MATLAB
■ proprietary language, developed by The MathWorks
■ not general-purpose programming language
■ application domain: numerical computing
■ used to design and simulate systems
■ not used to implement real-world systems
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 11
Why Learn C++?
■ vendor neutral
■ international standard
■ general purpose
■ powerful yet efficient
■ loosely speaking, includes C as subset; so can learn two languages (C++
and C) for price of one
■ easy to move from C++ to other languages but often not in other direction
■ many other popular languages inspired by C++
■ popular language
■ consistently ranks amongst top languages in TIOBE Software
Programming Community Index
(https://www.tiobe.com/tiobe-index/)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 12
Part 2
C++
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 13
Section 2.1
History of C++
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 14
Motivation
■ developed by Bjarne Stroustrup starting in 1979 at Computing Science
Research Center of Bell Laboratories, Murray Hill, NJ, USA
■ doctoral work in Computing Laboratory of University of Cambridge,
Cambridge, UK
■ study alternatives for organization of system software for distributed
systems
■ required development of relatively large and detailed simulator
■ dissertation:
B. Stroustrup. Communication and Control in Distributed Computer
Systems. PhD thesis, University of Cambridge, Cambridge, UK, 1979.
■ in 1979, joined Bell Laboratories after having finished doctorate
■ work started with attempt to analyze UNIX kernel to determine to what
extent it could be distributed over network of computers connected by LAN
■ needed way to model module structure of system and pattern of
communication between modules
■ no suitable tools available
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 15
Objectives
■ had bad experiences writing simulator during Ph.D. studies; originally
used Simula for simulator; later forced to rewrite in BCPL for speed; more
low level than C; BCPL was horrible to use
■ notion of what properties good tool would have motivated by these
experiences
■ suitable tool for projects like simulator, operating system, other systems
programming tasks should:
2 support for effective program organization (like in Simula) (i.e., classes,
some form of class hierarchies, some form of support for concurrency,
strong checking of type system based on classes)
2 produce programs that run fast (like with BCPL)
2 be able to easily combine separately compilable units into program (like
with BCPL)
2 have simple linkage convention, essential for combining units written in
languages such as C, Algol68, Fortran, BCPL, assembler into single
program
2 allow highly portable implementations (only very limited ties to operating
system)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 16
Timeline for C with Classes (1979–1983) I
May 1979 work on C with Classes starts
Oct 1979 initial version of Cpre, preprocessor that added Simula-like
classes to C; language accepted by preprocessor later started
being referred to as C with Classes
Mar 1980 Cpre supported one real project and several experiments (used
on about 16 systems)
Apr 1980 first internal Bell Labs paper on C with Classes published (later
to appear in ACM SIGPLAN Notices in Jan. 1982)
B. Stroustrup. Classes: An abstract data type facility for the
C language. Bell Laboratories Computer Science Technical
Report CSTR-84, Apr. 1980.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 17
Timeline for C with Classes (1979–1983) II
1980 initial 1980 implementation had following features:
■ classes
■ derived classes
■ public/private access control
■ constructors and destructors
■ call and return functions (call function implicitly called before
every call of every member function; return function implicitly
called after every return from every member function; can be
used for synchronization)
■ friend classes
■ type checking and conversion of function arguments
1981 in 1981, added:
■ inline functions
■ default arguments
■ overloading of assignment operator
Jan 1982 first external paper on C with Classes published
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 18
Timeline for C with Classes (1979–1983) III
B. Stroustrup. Classes: An abstract data type facility for the
C language. ACM SIGPLAN Notices, 17(1):42–51, Jan.
1982.
Feb 1983 more detailed paper on C with Classes published
B. Stroustrup. Adding classes to the C language: An
exercise in language evolution. Software: Practice and
Experience, 13(2):139–161, Feb. 1983.
■ C with Classes proved very successful; generated considerable interest
■ first real application of C with Classes was network simulators
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 19
Timeline for C84 to C++98 (1982–1998) I
■ started to work on cleaned up and extended successor to C with Classes,
initially called C84 and later renamed C++
Spring 1982 started work on Cfront compiler front-end for C84;
initially written in C with Classes and then transcribed to C84;
traditional compiler front-end performing complete check of
syntax and semantics of language, building internal
representation of input, analyzing and rearranging
representation, and finally producing output for some code
generator;
generated C code as output;
difficult to bootstrap on machine without C84 compiler; Cfront
software included special “half-processed” version of C code
resulting from compiling Cfront, which could be compiled with
native C compiler and resulting executable then used to compile
Cfront
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 20
Timeline for C84 to C++98 (1982–1998) II
Dec 1983 C84 (C with Classes) renamed C++;
name used in following paper prepared in Dec. 1983
B. Stroustrup. Data abstraction in C. Bell Labs Technical
Journal, 63(8):1701–1732, Oct. 1984.
(name C++ suggested by Rick Mascitti)
1983 virtual functions added
Note: going from C with Classes to C84 added: virtual functions,
function name and operator overloading, references, constants
(const), user-controlled free-store memory control, improved
type checking
Jan 1984 first C++ manual
B. Stroustrup. The C++ reference manual. AT&T Bell Labs
Computer Science Technical Report No. 108, Jan. 1984.
Sep 1984 paper describing operator overloading published
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 21
Timeline for C84 to C++98 (1982–1998) III
B. Stroustrup. Operator overloading in C++. In Proc. IFIP
WG2.4 Conference on System Implementation Languages:
Experience & Assessment, Sept. 1984.
1984 stream I/O library first implemented and later presented in
B. Stroustrup. An extensible I/O facility for C++. In Proc. of
Summer 1985 USENIX Conference, pages 57–70, June
1985.
Feb 1985 Cfront Release E (first external release); “E” for “Educational”;
available to universities
Oct 1985 Cfront Release 1.0 (first commercial release)
Oct 1985 first edition of C++PL written
B. Stroustrup. The C++ Programming Language. Addison
Wesley, 1986.
(Cfront Release 1.0 corresponded to language as defined in this
book)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 22
Timeline for C84 to C++98 (1982–1998) IV
Oct 1985 tutorial paper on C++
B. Stroustrup. A C++ tutorial. In Proceedings of the ACM
annual conference on the range of computing: mid-80’s
perspective, pages 56–64, Oct. 1985.
Jun 1986 Cfront Release 1.1; mainly bug fix release
Aug 1986 first exposition of set of techniques for which C++ was aiming to
provide support (rather than what features are already
implemented and in use)
B. Stroustrup. What is object-oriented programming? In
Proc. of 14th Association of Simula Users Conference,
Stockholm, Sweden, Aug. 1986.
Sep 1986 first Object-Oriented Programming, Systems, Languages, and
Applications (OOPSLA) conference (start of OO hype centered
on Smalltalk)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 23
Timeline for C84 to C++98 (1982–1998) V
Nov 1986 first commercial Cfront PC port (Cfront 1.1, Glockenspiel [in
Ireland])
Feb 1987 Cfront Release 1.2; primarily bug fixes but also added:
■ pointers to members
■ protected members
Nov 1987 first conference devoted to C++:
USENIX C++ conference (Santa Fe, NM, USA)
Dec 1987 first GNU C++ release (1.13)
Jan 1988 first Oregon Software (a.k.a. TauMetric) C++ release
Jun 1988 first Zortech C++ release
Oct 1988 first presented templates at USENIX C++ conference (Denver,
CO, USA) in paper:
B. Stroustrup. Parameterized types for C++. In Proc. of
USENIX C++ Conference, pages 1–18, Denver, CO, USA,
Oct. 1988.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 24
Timeline for C84 to C++98 (1982–1998) VI
Oct 1988 first USENIX C++ implementers workshop (Estes Park, CO,
USA)
Jan 1989 first C++ journal “The C++ Report” (from SIGS publications)
started publishing
Jun 1989 Cfront Release 2.0 major cleanup; new features included:
■ multiple inheritance
■ type-safe linkage
■ better resolution of overloaded functions
■ recursive definition of assignment and initialization
■ better facilities for user-defined memory management
■ abstract classes
■ static member functions
■ const member functions
■ protected member functions (first provided in release 1.2)
■ overloading of operator ->
■ pointers to members (first provided in release 1.2)
1989 main features of Cfront 2.0 summarized in
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 25
Timeline for C84 to C++98 (1982–1998) VII
B. Stroustrup. The evolution of C++: 1985–1989. USENIX
Computer Systems, 2(3), Summer 1989.
first presented in
B. Stroustrup. The evolution of C++: 1985–1987. In Proc. of
USENIX C++ Conference, pages 1–22, Santa Fe, NM,
USA, Nov. 1987.
Nov 1989 paper describing exceptions published
A. Koenig and B. Stroustrup. Exception handling for C++. In
Proc. of “C++ at Work” Conference, Nov. 1989.
followed up by
A. Koenig and B. Stroustrup. Exception handling for C++. In
Proc. of USENIX C++ Conference, Apr. 1990.
Dec 1989 ANSI X3J16 organizational meeting (Washington, DC, USA)
Mar 1990 first ANSI X3J16 technical meeting (Somerset, NJ, USA)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 26
Timeline for C84 to C++98 (1982–1998) VIII
Apr 1990 Cfront Release 2.1; bug fix release to bring Cfront mostly into
line with ARM
May 1990 annotated reference manual (ARM) published
M. A. Ellis and B. Stroustrup. The Annotated C++
Reference Manual. Addison Wesley, May 1990.
(formed basis for ANSI standardization)
May 1990 first Borland C++ release
Jul 1990 templates accepted (Seattle, WA, USA)
Nov 1990 exceptions accepted (Palo Alto, CA, USA)
Jun 1991 second edition of C++PL published
B. Stroustrup. The C++ Programming Language. Addison
Wesley, 2nd edition, June 1991.
Jun 1991 first ISO WG21 meeting (Lund, Sweden)
Sep 1991 Cfront Release 3.0; added templates (as specified in ARM)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 27
Timeline for C84 to C++98 (1982–1998) IX
Oct 1991 estimated number of C++ users 400,000
Feb 1992 first DEC C++ release (including templates and exceptions)
Mar 1992 run-time type identification (RTTI) described in
B. Stroustrup and D. Lenkov. Run-time type identification for
C++. The C++ Report, Mar. 1992.
(RTTI in C++ based on this paper)
Mar 1992 first Microsoft C++ release (did not support templates or
exceptions)
May 1992 first IBM C++ release (including templates and exceptions)
Mar 1993 RTTI accepted (Portland, OR, USA)
Jul 1993 namespaces accepted (Munich, Germany)
1993 further work on Cfront Release 4.0 abandoned after failed
attempt to add exception support
Aug 1994 ANSI/ISO Committee Draft registered
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 28
Timeline for C84 to C++98 (1982–1998) X
Aug 1994 Standard Template Library (STL) accepted (Waterloo, ON, CA);
described in
A. Stepanov and M. Lee. The standard template library.
Technical Report HPL-94-34 (R.1), HP Labs, Aug. 1994.
Aug 1996 export accepted (Stockholm, Sweden)
1997 third edition of C++PL published
B. Stroustrup. The C++ Programming Language. Addison
Wesley Longman, Reading, MA, USA, 3rd edition, 1997.
Nov 1997 final committee vote on complete standard (Morristown, NJ,
USA)
Jul 1998 Microsoft releases VC++ 6.0, first Microsoft compiler to provide
close-to-complete set of ISO C++
Sep 1998 ISO/IEC 14882:1998 (informally known as C++98) published
ISO/IEC 14882:1998 — programming languages — C++,
Sept. 1998.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 29
Timeline for C84 to C++98 (1982–1998) XI
1998 Beman Dawes starts Boost (provides peer-reviewed portable
C++ source libraries)
Feb 2000 special edition of C++PL published
B. Stroustrup. The C++ Programming Language. Addison
Wesley, Reading, MA, USA, special edition, Feb. 2000.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 30
Timeline After C++98 (1998–Present) I
Apr 2001 motion passed to request new work item: technical report on
libraries (Copenhagen, Denmark); later to become ISO/IEC TR
19768:2007
Oct 2003 ISO/IEC 14882:2003 (informally known as C++03) published;
essentially bug fix release; no changes to language from
programmer’s point of view
ISO/IEC 14882:2003 — programming languages — C++,
Oct. 2003.
2003 work on C++0x (now known as C++11) starts
Oct 2004 estimated number of C++ users 3,270,000
Apr 2005 first votes on features for C++0x (Lillehammer, Norway)
2005 auto, static_assert, and rvalue references accepted in
principle
Apr 2006 first full committee (official) votes on features for C++0x (Berlin,
Germany)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 31
Timeline After C++98 (1998–Present) II
Sep 2006 performance technical report (TR 18015) published:
ISO/IEC TR 18015:2006 — information technology —
programming languages, their environments and system
software interfaces — technical report on C++ performance,
Sept. 2006.
work spurred by earlier proposal to standardize subset of C++
for embedded systems called Embedded C++ (or just EC++);
EC++ motivated by performance concerns
Apr 2006 decision to move special mathematical functions to separate ISO
standard (Berlin, Germany); deemed too specialized for most
programmers
Nov 2007 ISO/IEC TR 19768:2007 (informally known as C++TR1)
published;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 32
Timeline After C++98 (1998–Present) III
ISO/IEC TR 19768:2007 — information technology —
programming languages — technical report on C++ library
extensions, Nov. 2007.
specifies series of library extensions to be considered for
adoption later in C++
2009 another particularly notable book on C++ published
B. Stroustrup. Programming: Principles and Practice Using
C++. Addison Wesley, Upper Saddle River, NJ, USA, 2009.
Aug 2011 ISO/IEC 14882:2011 (informally known as C++11) ratified
ISO/IEC 14882:2011 — information technology —
programming languages — C++, Sept. 2011.
2013 fourth edition of C++PL published
B. Stroustrup. The C++ Programming Language. Addison
Wesley, 4th edition, 2013.
2014 ISO/IEC 14882:2014 (informally known as C++14) ratified
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 33
Timeline After C++98 (1998–Present) IV
ISO/IEC 14882:2014 — information technology —
programming languages — C++, Dec. 2014.
2017 ISO/IEC 14882:2017 (informally known as C++17) ratified
ISO/IEC 14882:2017 — information technology —
programming languages — C++, Dec. 2017.
2020 ISO/IEC 14882:2020 (informally known as C++20) ratified
ISO/IEC 14882:2020 — information technology —
programming languages — C++, Dec. 2020.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 34
Additional Comments
■ reasons for using C as starting point:
2 flexibility (can be used for most application areas)
2 efficiency
2 availability (C compilers available for most platforms)
2 portability (source code relatively portable from one platform to another)
■ main sources for ideas for C++ (aside from C) were Simula, Algol68,
BCPL, Ada, Clu, ML; in particular:
2 Simula gave classes
2 Algol68 gave operator overloading, references, ability to declare variables
anywhere in block
2 BCPL gave // comments
2 exceptions influenced by ML
2 templates influenced by generics in Ada and parameterized modules in Clu
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 35
C++ User Population
Time Estimated Number of Users
Oct 1979 1
Oct 1980 16
Oct 1981 38
Oct 1982 85
Oct 1983 ??+2 (no Cpre count)
Oct 1984 ??+50 (no Cpre count)
Oct 1985 500
Oct 1986 2,000
Oct 1987 4,000
Oct 1988 15,000
Oct 1989 50,000
Oct 1990 150,000
Oct 1991 400,000
Oct 2004 over 3,270,000
■ above numbers are conservative
■ 1979 to 1991: C++ user population doubled approximately every 7.5
months
■ stable growth thereafter
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 36
Success of C++
■ C++ very successful programming language
■ not luck or solely because based on C
■ efficient, provides low-level access to hardware, but also supports
abstraction
■ non-proprietary: in 1989, all rights to language transferred to standards
bodies (first ANSI and later ISO) from AT&T
■ multi-paradigm language, supporting procedural, object-oriented, generic,
and functional (e.g., lambda functions) programming
■ does not force particular programming style
■ reasonably portable
■ has continued to evolve, incorporating new ideas (e.g., templates,
exceptions, STL)
■ stable: high degree of compatibility with earlier versions of language
■ very strong bias towards providing general-purpose facilities rather than
more application-specific ones
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 37
Application Areas
■ banking and financial (funds transfer, financial modelling, teller machines)
■ classical systems programming (compilers, operating systems, device
drivers, network layers, editors, database systems)
■ small business applications (inventory systems)
■ desktop publishing (document viewers/editors, image editing)
■ embedded systems (cameras, cell phones, airplanes, medical systems,
appliances, space technologies)
■ entertainment (games)
■ graphics programming
■ hardware design and verification
■ scientific and numeric computation (physics, engineering, simulations,
data analysis, geometry processing)
■ servers (web servers, billing systems)
■ telecommunication systems (phones, networking, monitoring, billing,
operations systems)
■ middleware
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 38
Section 2.1.1
References
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 39
Evolution of C++
■ B. Stroustrup. A history of C++: 1979–1991. In Proc. of ACM History of
Programming Languages Conference, pages 271–298, Mar. 1993
■ B. Stroustrup. The Design and Evolution of C++. Addison Wesley, Mar.
1994.
■ B. Stroustrup. Evolving a language in and for the real world: C++
1991–2006. In Proc. of the ACM SIGPLAN Conference on History of
Programming Languages, pages 4–1–4–59, 2007.
■ Cfront software available from Computer History Museum’s Software
Preservation Group http://www.softwarepreservation.org.
(See http://www.softwarepreservation.org/projects/c_plus_
plus/cfront).
■ ISO JTC1/SC22/WG21 web site.
http://www.open-std.org/jtc1/sc22/wg21/.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 40
Standards Documents I
■ ISO/IEC 14882:1998 — programming languages — C++, Sept. 1998.
■ ISO/IEC 14882:2003 — programming languages — C++, Oct. 2003.
■ ISO/IEC TR 18015:2006 — information technology — programming
languages, their environments and system software interfaces —
technical report on C++ performance, Sept. 2006.
■ ISO/IEC TR 19768:2007 — information technology — programming
languages — technical report on C++ library extensions, Nov. 2007.
■ ISO/IEC 29124:2010 — information technology — programming
languages, their environments and system software interfaces —
extensions to the C++ library to support mathematical special functions,
Sept. 2010.
■ ISO/IEC TR 24733:2011 — information technology — programming
languages, their environments and system software interfaces —
extensions for the programming language C++ to support decimal
floating-point arithmetic, Nov. 2011.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 41
Standards Documents II
■ ISO/IEC 14882:2011 — information technology — programming
languages — C++, Sept. 2011.
■ ISO/IEC 14882:2014 — information technology — programming
languages — C++, Dec. 2014.
■ ISO/IEC TS 18822:2015 — programming languages — C++ — file system
technical specification, July 2015.
■ ISO/IEC TS 19570:2015 — programming languages — technical
specification for C++ extensions for parallelism, July 2015.
■ ISO/IEC TS 19841:2015 — technical specification for C++ extensions for
transactional memory, Oct. 2015.
■ ISO/IEC TS 19568:2015 — programming languages — C++ extensions
for library fundamentals, Oct. 2015.
■ ISO/IEC TS 19217:2015 — programming languages — C++ extensions
for concepts, Nov. 2015.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 42
Standards Documents III
■ ISO/IEC TS 19571:2016 — programming languages — technical
specification for C++ extensions for concurrency, Feb. 2016.
■ ISO/IEC TS 19568:2017 — programming languages — C++ extensions
for library fundamentals, Mar. 2017.
■ ISO/IEC TS 21425:2017 — programming languages — C++ extensions
for ranges, Nov. 2017.
■ ISO/IEC TS 22277:2017 — technical specification — C++ extensions for
coroutines, Nov. 2017.
■ ISO/IEC 14882:2017 — information technology — programming
languages — C++, Dec. 2017.
■ ISO/IEC TS 19216:2018 — programming languages — C++ extensions
for networking, Apr. 2018.
■ ISO/IEC TS 21544:2018 — programming languages — extensions to C++
for modules, May 2018.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 43
Standards Documents IV
■ ISO/IEC 14882:2020 — information technology — programming
languages — C++, Dec. 2020.
■ ISO JTC1/SC22/WG21 web site.
http://www.open-std.org/jtc1/sc22/wg21/.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 44
Section 2.2
Getting Started
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 45
hello Program: hello.cpp
1 #include <iostream>
2
3 int main() {
4 std::cout << "Hello, world!\n";
5 return std::cout.flush() ? 0 : 1;
6 }
■ program prints message “Hello, world!” and then exits
■ starting point for execution of C++ program is function called main; every
C++ program must define function called main
■ #include preprocessor directive to include complete contents of file
■ iostream standard header file that defines various types and variables
related to I/O
■ std::cout is standard output stream (defaults to user’s terminal)
■ operator << is used for output
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 46
Software Build Process (Without Modules)
Source Code Compile Link
Object File Executable
File
(.o) Program
(.cpp, .hpp)
Source Code Compile Object File
File
(.o)
(.cpp, .hpp)
.. .. .. ..
. . . .
Source Code Compile Object File
File
(.o)
(.cpp, .hpp)
■ start with C++ source code files (.cpp, .hpp)
■ compile: convert source code to object code
■ object code stored in object file (.o)
■ link: combine contents of one or more object files (and possibly some
libraries) to produce executable program
■ executable program can then be run directly
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 47
GNU Compiler Collection (GCC) C++ Compiler
■ g++ command provides both compiling and linking functionality
■ command-line usage:
g++ [options] input_file . . .
■ many command-line options are supported
■ some particularly useful command-line options listed on next slide
■ compile C++ source file file.cpp to produce object code file file.o:
g++ -c file.cpp
■ link object files file_1.o, file_2.o, . . . to produce executable file executable:
g++ -o executable file_1.o file_2.o . . .
■ web site:
http://www.gnu.org/software/gcc
■ C++ standards support in GCC:
https://gcc.gnu.org/projects/cxx-status.html
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 48
Common g++ Command-Line Options
■ -c
2 compile only (i.e., do not link)
■ -o file
2 use file file for output
■ -g
2 include debugging information
■ -On
2 set optimization level to n (0 almost none; 3 full)
■ -std=c++20
2 conform to C++20 standard
■ -Idir
2 specify additional directory dir to search for include files
■ -Ldir
2 specify additional directory dir to search for libraries
■ -llib
2 link with library lib
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 49
Common g++ Command-Line Options (Continued 1)
■ -pthread
2 enable concurrency support (via pthreads library)
■ -pedantic-errors
2 strictly enforce compliance with standard
■ -Wall
2 enable most warning messages
■ -Wextra
2 enable some extra warning messages not enabled by -Wall
■ -Wpedantic
2 warn about deviations from strict standard compliance
■ -Werror
2 treat all warnings as errors
■ -fno-elide-constructors
2 in contexts where standard allows (but does not require) optimization that
omits creation of temporary, do not attempt to perform this optimization
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 50
Common g++ Command-Line Options (Continued 2)
■ -fconstexpr-loop-limit=n
2 set maximum number of iterations for loop in constexpr functions to n
■ -fconstexpr-depth=n
2 set maximum nested evaluation depth for constexpr functions to n
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 51
Clang C++ Compiler
■ clang++ command provides both compiling and linking functionality
■ command-line usage:
clang++ [options] input_file . . .
■ many command-line options are supported
■ command-line interface is largely compatible with that of GCC g++
command
■ web site:
http://clang.llvm.org
■ C++ standards support in Clang:
http://clang.llvm.org/cxx_status.html
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 52
Common clang++ Command-Line Options
■ many of more frequently used command-line options for clang++
identical to those for g++
■ consequently, only small number of clang++ options given below
■ -fconstexpr-steps=n
2 sets maximum number of computation steps in constexpr functions to n
■ -fconstexpr-depth=n
2 sets maximum nested evaluation depth for constexpr functions to n
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 53
Manually Building hello Program
■ numerous ways in which hello program could be built
■ often advantageous to compile each source file separately
■ can compile and link as follows:
1 compile source code file hello.cpp to produce object file hello.o:
g++ -c hello.cpp
2 link object file hello.o to produce executable program hello:
g++ -o hello hello.o
■ generally, manual building of program is quite tedious, especially when
program consists of multiple source files and additional compiler options
need to be specified
■ in practice, we use tools to automate build process (e.g., CMake and
Make)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 54
Section 2.3
C++ Basics
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 55
The C++ Programming Language
■ created by Bjarne Stroustrup of Bell Labs
■ originally known as C with Classes; renamed as C++ in 1983
■ most recent specification of language in ISO/IEC 14882:2020 (informally
known as “C++20”)
■ next version of standard expected in approximately 2023 (informally
known as “C++23”)
■ procedural
■ loosely speaking is superset of C
■ directly supports object-oriented and generic programming
■ maintains efficiency of C
■ application domains: systems software, application software, device
drivers, embedded software, high-performance server and client
applications, entertainment software such as video games, native code for
Android applications
■ greatly influenced development of C# and Java
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 56
Comments
■ two styles of comments provided
■ comment starts with // and proceeds to end of line
■ comment starts with /* and proceeds to first */
// This is an example of a comment.
/* This is another example of a comment. */
/* This is an example of a comment that
spans
multiple lines. */
■ comments of /* · · · */ style do not nest
/*
/* This sentence is part of a comment. */
This sentence is not part of any comment and
will probably cause a compile error.
*/
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 57
Identifiers
■ identifiers used to name entities such as: types, objects (i.e., variables),
and functions
■ valid identifier is sequence of one or more letters, digits, and underscore
characters that does not begin with a digit [C++17 §5.10/1]
⁓⁓⁓⁓⁓⁓⁓⁓
■ identifiers that begin with underscore (in many cases) or contain double
underscores are reserved for use by C++ implementation and should be
avoided ⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §5.10/3]
■ examples of valid identifiers:
2 event_counter
2 eventCounter
2 sqrt_2
2 f_o_o_b_a_r_4_2
■ identifiers are case sensitive (e.g., counter and cOuNtEr are distinct
identifiers)
■ identifiers cannot be any of reserved keywords (see next slide)
■ scope of identifier is context in which identifier is valid (e.g., block,
function, global)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 58
Reserved Keywords
alignas constexpr mutable switch
alignof constinit namespace template
and const_cast new this
and_eq continue noexcept thread_local
asm decltype not throw
auto default not_eq true
bitand delete nullptr try
bitor do operator typedef
bool double or typeid
break dynamic_cast or_eq typename
case else private union
catch enum protected unsigned
char explicit public using
char8_t export register virtual
char16_t extern reinterpret_cast void
char32_t false requires volatile
class float return wchar_t
co_await for short while
co_return friend signed xor
co_yield goto sizeof xor_eq
compl if static final∗
concept inline static_assert import∗
const int static_cast module∗
consteval long struct override∗
∗ Note: context sensitive
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 59
Section 2.3.1
Preprocessor
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 60
The Preprocessor
■ prior to compilation, source code transformed by preprocessor
■ preprocessor output then passed to compiler for compilation
■ preprocessor behavior can be controlled by preprocessor directives
■ preprocessor directive occupies single line and consists of:
1 hash character (i.e., “#”)
2 preprocessor instruction (i.e., define, undef, include, if, ifdef,
ifndef, else, elif, endif, line, error, and pragma)
3 arguments (depending on instruction)
4 line break
■ preprocessor can be used to:
2 conditionally compile parts of source file
2 define macros and perform macro expansion
2 include other files
2 force error to be generated
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 61
Source-File Inclusion
■ can include contents of another file in source using preprocessor
#include directive
■ syntax:
#include <path_specifier>
or
#include "path_specifier"
■ path_specifier is pathname (which may include directory) identifying file
whose content is to be substituted in place of include directive
■ typically, angle brackets used for system header files and double quotes
used otherwise
■ example:
#include <iostream>
#include <boost/tokenizer.hpp>
#include "my_header_file.hpp"
#include "some_directory/my_header_file.hpp"
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 62
Defining Macros
■ can define macros using #define directive
■ syntax:
#define name value
■ name is name of macro and value is value of macro
■ example:
#define DEBUG_LEVEL 10
■ macros can also take arguments
■ generally, macros should be avoided when possible (i.e., when other
better mechanisms are available to achieve desired effect)
■ for example, although macros can be used as way to accomplish inlining
of functions, such usage should be avoided since language mechanism
exists for specifying inline functions
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 63
Conditional Compilation
■ can conditionally include code through use of if-elif-else construct
■ conditional preprocessing block consists of following (in order)
1 #if, #ifdef, or #ifndef directive
2 optionally any number of #elif directives
3 at most one #else directive
4 #endif directive
■ code in taken branch of if-elif-else construct passed to compiler, while
code in other branches discarded
■ example:
#if DEBUG_LEVEL == 1
// ...
#elif DEBUG_LEVEL == 2
// ...
#else
// ...
#endif
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 64
Preprocessor Predicate __has_include
■ preprocessor predicate __has_include can be used in expressions for
preprocessor to test for existence of header files
■ example:
#ifdef __has_include
# if __has_include(<optional>)
# include <optional>
# define have_optional 1
# elif __has_include(<experimental/optional>)
# include <experimental/optional>
# define have_optional 1
# define experimental_optional
# else
# define have_optional 0
# endif
#endif
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 65
Section 2.3.2
Objects, Types, and Values
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 66
Fundamental Types
■ boolean type: bool
■ character types:
2 char (may be signed or unsigned)
2 signed char
2 unsigned char
2 char8_t, char16_t, char32_t
2 wchar_t
■ char is distinct type from signed char and unsigned char
■ standard signed integer types: ⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §6.9.1/2]
2 signed char
2 signed short int
2 signed int
2 signed long int
2 signed long long int
■ standard unsigned integer types:
2 unsigned char
2 unsigned short int
2 unsigned int
2 unsigned long int
2 unsigned long long int
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 67
Fundamental Types (Continued)
■ “int” may be omitted from names of (non-character) integer types (e.g.,
“unsigned” equivalent to “unsigned int” and “signed” equivalent
to “signed int”)
■ “signed” may be omitted from names of signed integer types, excluding
signed char (e.g., “int” equivalent to “signed int”)
■ boolean, character, and (signed and unsigned) integer types collectively
called integral types
■ integral types must use binary positional representation; two’s
complement, one’s complement, and sign magnitude representations
permitted [C++17 §6.9.1/7]
⁓⁓⁓⁓⁓⁓⁓⁓
■ floating-point types:
2 float
2 double
2 long double
■ void (i.e., incomplete/valueless) type: void
■ null pointer type: std::nullptr_t (defined in header file cstddef)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 68
Literals
■ literal (a.k.a. literal constant) is value written exactly as it is meant to be
interpreted
■ examples of literals:
"Hello, world"
"Bjarne"
’a’
’A’
123
123U
1’000’000’000
3.1415
1.0L
1.23456789e-10
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 69
Character Literals
■ character literal consists of optional prefix followed by one or more
characters enclosed in single quotes
■ type of character literal determined by prefix (or lack thereof) as follows:
Prefix Literal Type
None ordinary normally char (in special cases int)
u8 UTF-8 char8_t
u UCS-2 char16_t
U UCS-4 char32_t
L wide wchar_t
■ special characters can be represented by escape sequence:
Escape
Escape
Character Sequence
Character Sequence
newline (LF) \n
question mark (?) \?
horizontal tab (HT) \t
single quote (’) \’
vertical tab (VT) \v
double quote (") \"
backspace (BS) \b
octal number ooo \ooo
carriage return (CR) \r
hex number hhh \xhhh
form feed (FF) \f
code point nnnn \unnnn
alert (BEL) \a
code point nnnnnnnn \Unnnnnnnn
backslash (\) \\
■ examples of character literals:
’a’ ’1’ ’!’ ’\n’ u’a’ U’a’ L’a’ u8’a’
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 70
Character Literals (Continued)
■ decimal digit characters guaranteed to be consecutive in value (e.g., ’1’
must equal ’0’ + 1) [C++17 §5.3/3]
⁓⁓⁓⁓⁓⁓⁓
■ in case of ordinary character literals, alphabetic characters are not
guaranteed to be consecutive in value (e.g., ’b’ is not necessarily
’a’ + 1)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 71
String Literals
■ (non-raw) string literal consists of optional prefix followed by zero or more
characters enclosed in double quotes
■ string literal has character array type
■ type of string literal determined by prefix (or lack thereof) as follows:
Prefix Literal Type
None narrow const char[]
u8 UTF-8 const char8_t[]
u UTF-16 const char16_t[]
U UTF-32 const char32_t[]
L wide const wchar_t[]
■ examples of string literals:
"Hello, World!\n"
"123"
"ABCDEFG"
■ adjacent string literals are concatenated (e.g., "Hel" "lo" equivalent to
"Hello")
■ string literals implicitly terminated by null character (i.e., ’\0’)
■ so, for example, "Hi" means ’H’ followed by ’i’ followed by ’\0’
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 72
Raw String Literals
■ interpretation of escape sequences (e.g., “\n”) inside string literal can be
avoided by using raw literal
■ raw literal has form:
2 prefix R"delimiter(raw_characters)delimiter"
■ optional prefix is string-literal prefix (e.g., u8)
■ optional delimiter is sequence of characters used to assist in delimiting
string
■ raw_characters is sequence of characters comprising string
■ escape sequences not processed inside raw literal
■ raw literal can also contain newline characters
■ examples of raw string literals:
R"(He said, "No.")"
u8R"(He said, "No.")"
R"foo(The answer is 42.)foo"
R"((+|-)?[[:digit:]]+)"
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 73
Integer Literals
■ can be specified in decimal, binary, hexadecimal, and octal
■ number base indicated by prefix (or lack thereof) as follows:
Prefix Number Base
None decimal
Leading 0 octal
0b or 0B binary
0x or 0X hexadecimal
■ various suffixes can be specified to control type of literal:
2 u or U
2 l or L
2 both u or U and l or L
2 ll or LL
2 both u or U and ll or LL
■ can use single quote as digit separator (e.g., 1’000’000)
■ examples of integer literals:
42
1’000’000’000’000ULL
0xdeadU
■ integer literal always nonnegative; so, for example, -1 is integer literal 1
with negation operation applied [C++17 §5.13.2] [C++17 §5.13.4]
⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 74
Integer Literals (Continued)
Suffix Decimal Literal Non-Decimal Literal
None int int
long int unsigned int
long long int long int
unsigned long int
long long int
unsigned long long int
u or U unsigned int unsigned int
unsigned long int unsigned long int
unsigned long long int unsigned long long int
l or L long int long int
long long int unsigned long int
long long int
unsigned long long int
Both u or U unsigned long int unsigned long int
and l or L unsigned long long int unsigned long long int
ll or LL long long int long long int
unsigned long long int
Both u or U unsigned long long int unsigned long long int
and ll or LL
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 75
Floating-Point Literals
■ type of literal indicated by suffix (or lack thereof) as follows:
Suffix Type
None double
f or F float
l or L long double
■ examples of double literals:
1.414
1.25e-8
■ examples of float literals:
1.414f
1.25e-8f
■ examples of long double literals:
1.5L
1.25e-20L
■ floating-point literals always nonnegative; so, for example, -1.0 is literal
1.0 with negation operator applied
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 76
Hexadecimal Floating-Point Literals
■ hexadecimal floating-point literal has general form:
1 prefix 0x or 0X
2 hexadecimal digits for integer part of number (optional if at least one digit
after radix point)
3 period character (i.e., radix point)
4 hexadecimal digits for fractional part of number (optional if at least one digit
before radix point)
5 p character (which designates exponent to follow)
6 one or more decimal digits for base-16 exponent
7 optional floating-point literal suffix (e.g., f or l)
■ examples of hexadecimal floating-point literals:
Literal Type Value (Decimal)
0x.8p0 double 0.5
0x10.cp0 double 16.75
0x.8p0f float 0.5
0xf.fp0f float 15.9375
0x1p10L long double 1024
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 77
Boolean and Pointer Literals
■ boolean literals:
true
false
■ pointer literal:
nullptr
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 78
Declarations and Definitions
■ declaration introduces identifier for type, object (i.e., variable), or function
(without necessarily providing full information about identifier)
2 in case of object, specifies type (of object)
2 in case of function, specifies number of parameters, type of each
parameter, and type of return value (if not automatically deduced)
■ each identifier must be declared before it can be used (i.e., referenced)
■ definition provides full information about identifier and causes entity
associated with identifier (if any) to be created
2 in case of type, provides full details about type
2 in case of object, causes storage to be allocated for object and object to be
created
2 in case of function, provides code for function body
■ in case of objects, in most (but not all) contexts, declaring object also
defines it
■ can declare identifier multiple times but can define only once
■ above terminology often abused, with “declaration” and “definition” being
used interchangeably
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 79
Examples of Declarations and Definitions
int count; // declare and define count
extern double alpha; // (only) declare alpha
void func() { // declare and define func
int n; // declare and define n
double x = 1.0; // declare and define x
// ...
}
bool isOdd(int); // declare isOdd
bool isOdd(int x); // declare isOdd (x ignored)
bool isOdd(int x) { // declare and define isOdd
return x % 2;
}
struct Thing; // declare Thing
struct Vector2 { // declare and define Vector2
double x;
double y;
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 80
Variable Declarations and Definitions
■ variable declaration (a.k.a. object declaration) introduces identifier that
names object and specifies type of object
■ variable definition (a.k.a. object definition) provides all information
included in variable declaration and also causes object to be created (e.g.,
storage allocated for object)
■ example:
int count;
// declare and define count
double alpha;
// declare and define alpha
extern double gamma;
// declare (but do not define) gamma
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 81
Arrays
■ array is collection of one or more objects of same type that are stored
contiguously in memory
■ each element in array identified by (unique) integer index, with indices
starting from zero
■ array denoted by []
■ example:
double x[10]; // array of 10 doubles
int data[512][512]; // 512 by 512 array of ints
■ elements of array accessed using subscripting operator []
■ example:
int x[10];
// elements of arrays are x[0], x[1], ..., x[9]
■ often preferable to use user-defined type for representing array instead of
array type
■ for example, std::array and std::vector types (to be discussed later)
have numerous practical advantages over array types
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 82
Array Example
■ code:
int a[4] = {1, 2, 3, 4};
■ assumptions (for some completely fictitious C++ language
implementation):
2 sizeof(int) is 4
2 array a starts at address 1000
■ memory layout:
Address Name
1000 1 a[0]
1004 2 a[1]
1008 3 a[2]
1012 4 a[3]
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 83
Pointers
■ pointer is object whose value is address in memory where another object
is stored
■ pointer to object of type T denoted by T*
■ null pointer is special pointer value that does not refer to any valid
memory location
■ null pointer value provided by nullptr keyword
■ accessing object to which pointer refers called dereferencing
■ dereferencing pointer performed by indirection operator (i.e., “*”)
■ if p is pointer, *p is object to which pointer refers
■ if x is object of type T, &x is (normally) address of object, which has type
T*
■ example:
char c;
char* cp = nullptr; // cp is pointer to char
char* cp2 = &c; // cp2 is pointer to char
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 84
Pointer Example
■ code:
int i = 42;
int* p = &i;
assert(*p == 42);
■ assumptions (for some completely fictitious C++ language
implementation):
2 sizeof(int) is 4
2 sizeof(int*) is 4
2 &i is ((int*)1000)
2 &p is ((int*)1004)
■ memory layout:
Address Name
1000 42 i
1004 1000 p
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 85
References
■ reference is alias (i.e., nickname) for already existing object
■ two kinds of references:
1 lvalue reference
2 rvalue reference
■ lvalue reference to object of type T denoted by T&
■ rvalue reference to object of type T denoted by T&&
■ initializing reference called reference binding
■ lvalue and rvalue references differ in their binding properties (i.e., to what
kinds of objects reference can be bound)
■ in most contexts, lvalue references usually needed
■ rvalue references used in context of move constructors and move
assignment operators (to be discussed later)
■ example:
int x;
int& y = x; // y is lvalue reference to int
int&& tmp = 3; // tmp is rvalue reference to int
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 86
References Example
■ code:
int i = 42;
int& j = i;
assert(j == 42);
■ assumptions (for some completely fictitious C++ language
implementation):
2 sizeof(int) is 4
2 &i is ((int*)1000)
■ memory layout:
Address Name
1000 42 i, j
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 87
References Versus Pointers
■ references and pointers similar in that both can be used to refer to some
other entity (e.g., object or function)
■ two key differences between references and pointers:
1 reference must refer to something, while pointer can have null value
(nullptr)
2 references cannot be rebound, while pointers can be changed to point to
different entity
■ references have cleaner syntax than pointers, since pointers must be
dereferenced upon each use (and dereference operations tend to clutter
code)
■ use of pointers often implies need for memory management (i.e., memory
allocation, deallocation, etc.), and memory management can introduce
numerous kinds of bugs when done incorrectly
■ often faced with decision of using pointer or reference in code
■ generally advisable to prefer use of references over use of pointers unless
compelling reason to do otherwise, such as:
2 must be able to handle case of referring to nothing
2 must be able to change entity being referenced
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 88
Unscoped Enumerations
■ enumerated type provides way to describe range of values that are
represented by named constants called enumerators
■ object of enumerated type can take any one of enumerators as value
■ enumerator values represented by some integral type
■ enumerator can be assigned specific value (which may be negative)
■ if enumerator not assigned specific value, value defaults to zero if first
enumerator in enumeration and one greater than value for previous
enumerator otherwise
■ example:
enum Suit {
Clubs, Diamonds, Hearts, Spades
};
Suit suit = Clubs;
■ example:
enum Suit {
Clubs = 1, Diamonds = 2, Hearts = 4, Spades = 8
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 89
Scoped Enumerations
■ scoped enumeration similar to unscoped enumeration, except:
2 all enumerators are placed in scope of enumeration itself
2 integral type used to hold enumerator values can be explicitly specified
2 conversions involving scoped enumerations are stricter (i.e., more type
safe)
■ class or struct added after enum keyword to make enumeration
scoped
■ scope resolution operator (i.e., “::”) used to access enumerators
■ scoped enumerations should probably be preferred to unscoped ones
■ example:
enum struct Season {
spring, summer, fall, winter
};
enum struct Suit : unsigned char {
clubs, diamonds, hearts, spades
};
Season season = Season::summer;
Suit suit = Suit::spades;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 90
Type Aliases with typedef Keyword
■ typedef keyword used to create alias for existing type
■ example:
typedef long long BigInt;
BigInt i; // i has type long long
typedef char* CharPtr;
CharPtr p; // p has type char*
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 91
Type Aliases with using Statement
■ using statement can be used to create alias for existing type
■ probably preferable to use using statement over typedef
■ example:
using BigInt = long long;
BigInt i; // i has type long long
using CharPtr = char*;
CharPtr p; // p has type char*
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 92
The extern Keyword
■ translation unit: basic unit of compilation in C++ (i.e., single source code
file plus all of its directly and indirectly included header files)
■ extern keyword used to declare object/function in separate translation
unit
■ example:
extern int evil_global_variable;
// declaration only
// actual definition in another file
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 93
The const Qualifier
■ const qualifier specifies that object has value that is constant (i.e.,
cannot be changed)
■ qualifier that applies to object itself said to be top level
■ following defines x as int with value 42 that cannot be modified:
const int x = 42;
■ example:
const int x = 42;
x = 13; // ERROR: x is const
const int& x1 = x; // OK
const int* p1 = &x; // OK
int& x2 = x; // ERROR: x const, x2 not const
int* p2 = &x; // ERROR: x const, *p2 not const
■ example:
int x = 0;
const int& y = x;
x = 42; // OK
// y also changed to 42 since y refers to x
// y cannot be used to change x, however
// i.e., the following would cause compile error:
// y = 24; // ERROR: y is const
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 94
The const Qualifier and Non-Pointer/Non-Reference Types
■ with types that are not pointer or reference types, const can only be
applied to object itself (i.e., top level)
■ that is, object itself may be const or non-const
■ example:
int i = 0; // object i is modifiable
i = 42; // OK: i can be modified
const int ci = 0; // object ci is not modifiable
ci = 42; // ERROR: ci cannot be modified
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 95
Example: const Qualifier and Non-Pointer/Non-Reference
Types
1 // with types that are not pointer or reference types, const
2 // can only be applied to object itself (i.e., top level)
3 // object itself may be const or non-const
4
5 int i = 0; // non-const int object
6 const int ci = 0; // const int object
7
8 i = 42; // OK: can modify non-const object
9 ci = 42; // ERROR: cannot modify const object
10
11 i = ci; // OK: can modify non-const object
12 ci = i; // ERROR: cannot modify const object
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 96
The const Qualifier and Pointer Types
■ every pointer is associated with two objects: pointer itself and pointee (i.e.,
object to which pointer points)
■ const qualifier can be applied to each of pointer (i.e., top-level qualifier)
and pointee
Address
..
int i = 42; // pointee .
1000 (pointer)
// p is pointer to int i 2000
// for example: (&p)
// int* p = &i; ..
// const int* p = &i; .
// int* const p = &i;
// const int* const p = &i; 2000 42
(&i) (pointee)
..
.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 97
Example: const Qualifier and Pointer Types
1 // with pointer types, const can be applied to each of:
2 // pointer and pointee
3 // pointer itself may be const or non-const (top-level)
4 // pointee may be const or non-const
5
6 int i = 0;
7 int j = 0;
8
9 int* pi = &i; // non-const pointer to a non-const int
10 pi = &j; // OK: can modify non-const pointer
11 *pi = 42; // OK: can modify non-const pointee
12
13 const int* pci = &i; // non-const pointer to a const int
14 // equivalently: int const* pci = &i;
15 pci = &j; // OK: can modify non-const pointer
16 *pci = 42; // ERROR: cannot modify const pointee
17
18 int* const cpi = &i; // const pointer to a non-const int
19 cpi = &j; // ERROR: cannot modify const pointer
20 *cpi = 42; // OK: can modify non-const pointee
21
22 const int* const cpci = &i; // const pointer to a const int
23 // equivalently: int const* const cpci = &i;
24 cpci = &j; // ERROR: cannot modify const pointer
25 *cpci = 42; // ERROR: cannot modify const pointee
26
27 pci = pi; // OK: adds const to pointee
28 pi = pci; // ERROR: discards const from pointee
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 98
The const Qualifier and Reference Types
■ reference is name that refers to object (i.e., referee)
■ in principle, const qualifier can be applied to reference itself (i.e.,
top-level qualifier) or referee
■ since reference cannot be rebound, reference itself is effectively always
constant
■ for this reason, does not make sense to explicitly apply const as
top-level qualifier for reference type and language disallows this
■ const qualifier can only be applied to referee
■ example:
int j = 0;
int k = 42;
int& i = j;
// i is reference; j is referee
// referee is modifiable
const int& ci = j;
// ci is reference; j is referee
// referee is not modifiable
const int& ci = k; // ERROR: cannot redefine/rebind
int& const r = j;
// ERROR: reference itself cannot be specified as const
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 99
Example: const Qualifier and Reference Types
1 // with reference types, const can only be applied to referee
2 // reference itself cannot be rebound (i.e., is constant)
3 // referee may be const or non-const
4
5 int i = 0; const int ci = 0;
6 int i1 = 0; const int ci1 = 0;
7
8 // reference to non-const int
9 int& ri = i;
10 ri = ci; // OK: can modify non-const referee
11 int& ri = i1; // ERROR: cannot redefine/rebind reference
12
13 // reference to const int
14 const int& rci = ci;
15 rci = i; // ERROR: cannot modify const referee
16 const int& rci = ci1;
17 // ERROR: cannot redefine/rebind reference
18
19 // ERROR: reference itself cannot be const qualified
20 int& const cri = i; // ERROR: invalid const qualifier
21
22 // ERROR: reference itself cannot be const qualified
23 const int& const crci = ci; // ERROR: invalid const qualifier
24 // also: int const& const crci = ci; // ERROR
25
26 const int& r1 = ci; // OK: adds const to referee
27 int& r2 = ci; // ERROR: discards const from referee
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 100
The const Qualifier and Pointer-to-Pointer Types
■ for given type T, cannot implicitly convert T** to const T**
■ although such conversion looks okay at first glance, actually would create
backdoor for changing const objects
■ can, however, implicitly convert T** to const T* const*
■ for example, code like that shown below could be used to change const
objects if T** to const T** were valid conversion:
const int i = 42;
int* p;
const int** q = &p;
// Fortunately, this line is not valid code.
// ERROR: cannot convert int** to const int**
*q = &i;
// Change p (to which q points) to point to i.
// OK: *q is not const (only **q is const)
*p = 0;
// Set i (to which p points) to 0.
// OK: *p is not const
// This line would change i, which is const.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 101
The volatile Qualifier
■ volatile qualifier used to indicate that object can change due to agent
external to program (e.g., memory-mapped device, signal handler)
■ compiler cannot optimize away read and write operations on volatile
objects (e.g., repeated reads without intervening writes cannot be
optimized away)
■ volatile qualifier typically used when object:
2 corresponds to register of memory-mapped device
2 may be modified by signal handler (namely, object of type
volatile std::sig_atomic_t)
■ example:
volatile int x;
volatile unsigned char* deviceStatus;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 102
The auto Keyword
■ in various contexts, auto keyword can be used as place holder for type
■ in such contexts, implication is that compiler must deduce type
■ example:
auto i = 3; // i has type int
auto j = i; // j has type int
auto& k = i; // k has type int&
const auto& n = i; // n has type const int&
auto x = 3.14; // x has type double
■ very useful in generic programming (covered later) when types not always
easy to determine
■ can potentially save typing long type names
■ can lead to more readable code (if well used)
■ if overused, can lead to bugs (sometimes very subtle ones) and difficult to
read code
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 103
Inline Variables
■ inline variable: variable that may be defined in multiple translation units
as long as all definitions are identical
■ potential for multiple definitions avoided by having linker simply choose
one of identical definitions and discard others (if more than one exists)
■ can request that variable be made inline by including inline qualifier in
variable declaration
■ inline variable must have static storage duration (e.g., static class member
or namespace-scope variable)
■ inline variable typically used to allow definition of variable to be placed in
header file without danger of multiple definitions
■ inline variable has same address in all translation units
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 104
Inline Variable: Example
inline_variable_1_1.hpp
1 inline int magic = 42;
main.cpp
1 #include <iostream>
2 #include "inline_variable_1_1.hpp"
3 int main() {
4 std::cout << magic << "\n";
5 }
other.cpp
1 #include "inline_variable_1_1.hpp"
2 void func() {/* ... */}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 105
Section 2.3.3
Operators and Expressions
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 106
Operators
Arithmetic Operators
Operator Name Syntax
addition a + b Bitwise Operators
subtraction a - b
Operator Name Syntax
unary plus +a
bitwise NOT ~a
unary minus -a
bitwise AND a & b
multiplication a * b
bitwise OR a | b
division a / b
bitwise XOR a ^ b
modulo (i.e., remainder) a % b
arithmetic left shift a << b
pre-increment ++a
arithmetic right shift a >> b
post-increment a++
pre-decrement --a
post-decrement a--
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 107
Operators (Continued 1)
Assignment and
Compound-Assignment Operators
Operator Name Syntax
assignment a = b
addition assignment a += b
subtraction assignment a -= b
multiplication assignment a *= b
division assignment a /= b
modulo assignment a %= b
bitwise AND assignment a &= b
bitwise OR assignment a |= b
bitwise XOR assignment a ^= b
arithmetic left shift assignment a <<= b
arithmetic right shift assignment a >>= b
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 108
Operators (Continued 2)
Logical/Relational Operators
Operator Name Syntax Member and Pointer Operators
three-way comparison a <=> b
Operator Name Syntax
equal a == b
array subscript a[b]
not equal a != b
indirection *a
greater than a > b
address of &a
less than a < b
member selection a.b
greater than or equal a >= b
member selection a->b
less than or equal a <= b
member selection a.*b
logical negation !a
member selection a->*b
logical AND a && b
logical OR a || b
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 109
Operators (Continued 3)
Other Operators
Operator Name Syntax
function call a(...)
comma a, b
ternary conditional a ? b : c
scope resolution a::b
sizeof sizeof(a)
parameter-pack sizeof sizeof...(a)
alignof alignof(T)
allocate storage new T
allocate storage (array) new T[a]
deallocate storage delete a
deallocate storage (array) delete[] a
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 110
Operators (Continued 4)
Other Operators (Continued)
Operator Name Syntax
type ID typeid(a)
type cast (T) a
const cast const_cast<T>(a)
static cast static_cast<T>(a)
dynamic cast dynamic_cast<T>(a)
reinterpret cast reinterpret_cast<T>(a)
throw throw a
noexcept noexcept(e)
await co_await e
yield co_yield e
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 111
Operator Precedence
Precedence Operator Name Associativity
1 :: scope resolution none
2 . member selection (object) left to right
-> member selection (pointer)
[] subscripting
() function call
++ post-increment
-- post-decrement
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 112
Operator Precedence (Continued 1)
Precedence Operator Name Associativity
3 sizeof size of object/type right to left
++ pre-increment
-- pre-decrement
~ bitwise NOT
! logical NOT
- unary minus
+ unary plus
& address of
* indirection
co_await await
new allocate storage
new[] allocate storage (array)
delete deallocate storage
delete[] deallocate storage (array)
() cast
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 113
Operator Precedence (Continued 2)
Precedence Operator Name Associativity
4 .* member selection (objects) left to right
->* member selection (pointers)
5 * multiplication left to right
/ division
% modulus
6 + addition left to right
- subtraction
7 << left shift left to right
>> right shift
8 <=> three-way comparison left to right
9 < less than left to right
<= less than or equal
> greater than
>= greater than or equal
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 114
Operator Precedence (Continued 3)
Precedence Operator Name Associativity
10 == equality left to right
!= inequality
11 & bitwise AND left to right
12 ^ bitwise XOR left to right
13 | bitwise OR left to right
14 && logical AND left to right
15 || logical OR left to right
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 115
Operator Precedence (Continued 4)
Precedence Operator Name Associativity
16 ? : ternary conditional right to left
throw throw
co_yield yield
= assignment
*= multiplication assignment
/= division assignment
%= modulus assignment
+= addition assignment
-= subtraction assignment
<<= left shift assignment
>>= right shift assignment
&= bitwise AND assignment
|= bitwise OR assignment
^= bitwise XOR assignment
17 , comma left to right
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 116
Alternative Tokens
Alternative Primary
and &&
bitor |
or ||
xor ^
compl ~
bitand &
and_eq &=
or_eq |=
xor_eq ^=
not !
not_eq !=
■ alternative tokens above probably best avoided as they lead to more
verbose code
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 117
Expressions
■ An expression is a sequence of operators and operands that specifies a
computation. [C++17
⁓⁓⁓⁓⁓⁓
§8/1]
■ An expression has a type and, if the type is not void, a value. ⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §6.9.1/9]
■ A constant expression is an expression that can be evaluated at compile
time (e.g., 1 + 1).
■ Example:
Expression Type Value
x int 0
int x = 0; y = x int& reference to y
int y = 0; x + 1 int 1
int* p = &x; x * x + 2 * x int 0
double d = 0.0; y = x * x int& reference to y
// Evaluate some x == 42 bool false
// expressions here. *p int& reference to x
p == &x bool true
x > 2 * y bool false
std::sin(d) double 0.0
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 118
Operator Precedence/Associativity Example
Expression Fully-Parenthesized Expression
a + b + c ((a + b) + c)
a = b = c (a = (b = c))
c = a + b (c = (a + b))
d = a && !b || c (d = ((a && (!b)) || c))
++*p++ (++(*(p++)))
a | ~b & c ^ d (a | (((~b) & c) ^ d))
a[0]++ + a[1]++ (((a[0])++) + ((a[1])++))
a + b * c / d % - g (a + (((b * c) / d) % (-g)))
++p[i] (++(p[i]))
--*++p (--(*(++p)))
a += b += c += d (a += (b += (c += d)))
z = a == b ? ++c : --d (z = ((a == b) ? (++c) : (--d)))
a + b <=> c + d > 0 ((a + b) <=> (c + d)) > 0
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 119
Division/Modulus Operator and Negative Numbers
■ for integral operands, division operator yields algebraic quotient with any
fractional part discarded (i.e., round towards zero)
■ if quotient a / b is representable in type of result,
(a / b) * b + a % b is equal to a
■ so, assuming b is not zero and no overflow, a % b equals
a - (a / b) * b
■ result of modulus operator not necessarily nonnegative
■ example:
1 static_assert(5 % 3 == 2);
2 static_assert(5 % (-3) == 2);
3 static_assert((-5) % 3 == -2);
4 static_assert((-5) % (-3) == -2);
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 120
Short-Circuit Evaluation
■ logical-and operator (i.e., &&): ⁓⁓⁓⁓⁓⁓⁓
[C++17 §8.14]
2 groups left-to-right
2 result true if both operands are true, and false otherwise
2 second operand is not evaluated if first operand is false (in case of built-in
logical-and operator)
■ logical-or operator (i.e., ||): [C++17 §8.15]
⁓⁓⁓⁓⁓⁓⁓
2 groups left-to-right
2 result is true if either operand is true, and false otherwise
2 second operand is not evaluated if first operand is true (in case of built-in
logical-or operator)
■ example:
int x = 0;
bool b = (x == 0 || ++x == 1);
// b equals true; x equals 0
b = (x != 0 && ++x == 1);
// b equals false; x equals 0
■ above behavior referred to as short circuit evaluation
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 121
Short-Circuit Evaluation Example: a || b || c
■ for three values a, b, c of type bool, consider evaluation of expression
a || b || c
■ code showing short-circuit evaluation and associated control-flow graph
given below
a
bool _result;
if (a) F
goto _true;
if (b) T b
goto _true;
if (c)
goto _true; T F
_result = false;
goto done; c
_true: T F
_result = true;
done:
True False
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 122
Short-Circuit Evaluation Example: a && b && c
■ for three values a, b, c of type bool, consider evaluation of expression
a && b && c
■ code showing short-circuit evaluation and associated control-flow graph
given below
a
bool _result;
if (!a) T
goto _false;
if (!b) b F
goto _false;
if (!c)
goto _false; T F
_result = true;
goto done; c
_false: F
_result = false;
done: T
True False
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 123
Short-Circuit Evaluation Example: (a || b) && c
■ for three values a, b, c of type bool, consider evaluation of expression
(a || b) && c
■ code showing short-circuit evaluation and associated control-flow graph
given below (...........
example of compiler-generated assembly code)
............................................
a
bool _result;
if (a)
goto _second; F
if (!b)
goto _false; T b
_second:
if (!c) T F
goto _false;
_result = true; c
goto done;
_false: F
_result = false; T
done:
True False
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 124
The static_assert Statement
■ static_assert allows testing of boolean condition at compile time
■ used to test sanity of code or test validity of assumptions made by code
■ static_assert has two arguments:
1 boolean constant expression (condition to test)
2 string literal for error message to print if boolean expression not true
■ second argument is optional
■ failed static assertion results in compile error
■ example:
static_assert(sizeof(int) >= 4, "int is too small");
static_assert(1 + 1 == 2, "compiler is buggy");
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 125
The sizeof Operator
■ sizeof operator is used to query size of object or object type (i.e.,
amount of storage required)
■ for object type T, sizeof(T) yields size of T in bytes (e.g.,
sizeof(int), sizeof(int[10]))
■ for expression e, sizeof e yields size of object required to hold result of
e in bytes (e.g., sizeof(&x) where x is some object)
■ sizeof(char), sizeof(signed char), and
sizeof(unsigned char) guaranteed to be 1
■ byte is at least 8 bits (usually exactly 8 bits except on more exotic
platforms)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 126
The constexpr Qualifier for Variables
■ constexpr qualifier indicates object has value that is constant
expression (i.e., can be evaluated at compile time)
■ constexpr implies const (but converse not necessarily true)
■ following defines x as constant expression with type const int and
value 42:
constexpr int x = 42;
■ example:
constexpr int x = 42;
int y = 1;
x = 0; // ERROR: x is const
const int& x1 = x; // OK
const int* p1 = &x; // OK
int& x2 = x; // ERROR: x const, x2 not const
int* p2 = &x; // ERROR: x const, *p2 not const
int a1[x]; // OK: x is constexpr
int a2[y]; // ERROR: y is not constexpr
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 127
The constinit Specifier
■ constinit specifier can be added to variable declaration to assert that
variable must be statically initialized
■ can be used for variable with static or thread-local storage duration
■ if constinit used for variable that is not statically initialized, compiler
error will result
■ constinit variable not implicitly const
■ cannot use constexpr qualifier with constinit (as constexpr
makes constinit redundant)
■ can be used in non-initializing declaration to indicate that variable is
statically initialized
■ example:
constexpr double func(double);
constinit double x = func(42.0);
// error if x not statically initialized
■ example:
extern thread_local constinit int x;
int f() {
return x;
// no check of guard variable needed
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 128
Section 2.3.4
Control-Flow Constructs: Selection and Looping
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 129
The if Statement
■ allows conditional execution of code
■ syntax has form:
if (expression)
statement1
else
statement2
■ if expression expression is true, execute statement statement1 ; otherwise,
execute statement statement2
■ else clause can be omitted leading to simpler form:
if (expression)
statement1
■ conditional execution based on more than one condition can be achieved
using construct like:
if (expression1 )
statement1
else if (expression2 )
statement2
...
else
statementn
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 130
The if Statement (Continued 1)
■ to include multiple statements in branch of if, must group statements
into single statement using brace brackets
if (expression) {
statement1,1
statement1,2
statement1,3
...
} else {
statement2,1
statement2,2
statement2,3
...
}
■ advisable to always include brace brackets even when not necessary, as
this avoids potential bugs caused by forgetting to include brackets later
when more statements added to branch of if
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 131
The if Statement (Continued 2)
■ if statement may include initializer:
if (initializer; expression)
statement1 ;
else
statement2 ;
■ above construct equivalent to:
{
initializer;
if (expression)
statement1 ;
else
statement2 ;
}
■ if condition in if statement is constant expression, constexpr keyword
can be added after if keyword to yield what is called constexpr-if
statement
■ constexpr-if statement is evaluated at compile time and branch of if
statement that is not taken is discarded
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 132
The if Statement: Example
■ example with else clause:
int x = someValue;
if (x % 2 == 0) {
std::cout << "x is even\n";
} else {
std::cout << "x is odd\n";
}
■ example without else clause:
int x = someValue;
if (x % 2 == 0) {
std::cout << "x is divisible by 2\n";
}
■ example that tests for more than one condition:
int x = someValue;
if (x > 0) {
std::cout << "x is positive\n";
} else if (x < 0) {
std::cout << "x is negative\n";
} else {
std::cout << "x is zero\n";
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 133
The if Statement: Example
■ example with initializer:
int execute_command();
if (int ret = execute_command(); ret == 0) {
std::cout << "command successful\n";
} else {
std::cout << "command failed with status " <<
ret << ’\n’;
}
■ example constexpr-if statement:
constexpr int x = 10;
if constexpr (x < 0) {
std::cout << "negative\n";
} else if constexpr(x > 0) {
std::cout << "positive\n";
} else {
std::cout << "zero\n";
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 134
The switch Statement
■ allows conditional execution of code based on integral/enumeration value
■ syntax has form:
switch (expression) {
case const_expr1 :
statements1
case const_expr2 :
statements2
...
case const_exprn :
statementsn
default:
statements
}
■ expression is expression of integral or enumeration type or implicitly
convertible to such type; const_expri is constant expression of same type
as expression after conversions/promotions
■ if expression expression equals const_expri , jump to beginning of
statements statementsi ; if expression expr does not equal const_expri for
any i, jump to beginning of statements statements
■ then, continue executing statements until break statement is
encountered
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 135
The switch Statement (Continued)
■ switch statement can also include initializer:
switch (initializer; expression)
statement
■ above construct equivalent to:
{
initializer;
switch (expression)
statement
}
■ remember that, in absence of break statement, execution in switch
statement falls through from one case to next; if fall through not
considered, bugs will result, such as in following code:
1 unsigned int x = 0;
2 switch (x & 1) {
3 case 0:
4 std::cout << "x is even\n";
5 // BUG: missing break statement
6 case 1:
7 std::cout << "x is odd\n";
8 break;
9 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 136
The switch Statement: Example
■ example without initializer:
int x = someValue;
switch (x) {
case 0:
// Note that there is no break here.
case 1:
std::cout << "x is 0 or 1\n";
break;
case 2:
std::cout << "x is 2\n";
break;
default:
std::cout << "x is not 0, 1, or 2\n";
break;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 137
The switch Statement: Example (Continued)
■ example with initializer:
int get_value();
switch (int x = get_value(); x) {
case 0:
case 1:
std::cout << "x is 0 or 1\n";
break;
case 2:
std::cout << "x is 2\n";
break;
default:
std::cout << "x is not 0, 1, or 2\n";
break;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 138
The while Statement
■ looping construct
■ syntax has form:
while (expression)
statement
■ if expression expression is true, statement statement is executed; this
process repeats until expression expression becomes false
■ to allow multiple statements to be executed in loop body, must group
multiple statements into single statement with brace brackets
while (expression) {
statement1
statement2
statement3
...
}
■ advisable to always use brace brackets, even when loop body consists of
only one statement
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 139
The while Statement: Example
// print hello 10 times
int n = 10;
while (n > 0) {
std::cout << "hello\n";
--n;
}
// loop forever, printing hello
while (true) {
std::cout << "hello\n";
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 140
The for Statement
■ looping construct
■ has following syntax:
for (statement1 ; expression; statement2 )
statement3
■ first, execute statement statement1 ; then, while expression expression is
true, execute statement statement3 followed by statement statement2
■ statement1 and statement2 may be omitted; expression treated as true if
omitted
■ to include multiple statements in loop body, must group multiple
statements into single statement using brace brackets; advisable to always
use brace brackets, even when loop body consists of only one statement:
for (statement1 ; expression; statement2 ) {
statement3,1
statement3,2
...
}
■ any objects declared in statement1 go out of scope as soon as for loop
ends
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 141
The for Statement (Continued)
■ consider for loop:
for (statement1 ; expression; statement2 )
statement3
■ above for loop can be equivalently expressed in terms of while loop
as follows (except for behavior of continue statement, yet to be
discussed):
{
statement1 ;
while (expression) {
statement3
statement2 ;
}
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 142
The for Statement: Example
■ example with single statement in loop body:
// Print the integers from 0 to 9 inclusive.
for (int i = 0; i < 10; ++i)
std::cout << i << ’\n’;
■ example with multiple statements in loop body:
int values[10];
// ...
int sum = 0;
for (int i = 0; i < 10; ++i) {
// Stop if value is negative.
if (values[i] < 0) {
break;
}
sum += values[i];
}
■ example with error in assumption about scoping rules:
for (int i = 0; i < 10; ++i) {
std::cout << i << ’\n’;
}
++i; // ERROR: i no longer exists
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 143
Range-Based for Statement
■ variant of for loop for iterating over elements in range
■ example:
int array[4] = {1, 2, 3, 4};
// Triple the value of each element in the array.
for (auto&& x : array) {
x *= 3;
}
■ range-based for loop nice in that it clearly expresses programmer intent
(i.e., iterate over each element of collection)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 144
Range-Based for Statement (Continued)
■ consider range-based for statement:
attr(optional)
for (init-statement(optional) range-declaration : range-expression)
loop-statement
■ above statement equivalent to:
{
init-statement
auto && __range = range-expression;
auto __begin = begin-expr;
auto __end = end-expr;
for (; __begin != __end; ++__begin) {
range-declaration = *__begin;
loop-statement
}
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 145
The do Statement
■ looping construct
■ has following general syntax:
do
statement
while (expression);
■ statement statement executed;
then, expression expression evaluated;
if expression expression is true, entire process repeats from beginning
■ to execute multiple statements in body of loop, must group multiple
statements into single statement using brace brackets
do {
statement1
statement2
...
} while (expression);
■ advisable to always use brace brackets, even when loop body consists of
only one statement
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 146
The do Statement: Example
■ example with single statement in loop body:
// delay by looping 10000 times
int n = 0;
do
++n;
while (n < 10000);
■ example with multiple statements in loop body:
// print integers from 0 to 9 inclusive
int n = 0;
do {
std::cout << n << ’\n’;
++n;
} while (n < 10);
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 147
The break Statement
■ break statement causes enclosing loop or switch to be terminated
immediately
■ example:
// Read integers from standard input until an
// error or end-of-file is encountered or a
// negative integer is read.
int x;
while (std::cin >> x) {
if (x < 0) {
break;
}
std::cout << x << ’\n’;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 148
The continue Statement
■ continue statement causes next iteration of enclosing loop to be
started immediately
■ example:
int values[10];
...
// Print the nonzero elements of the array.
for (int i = 0; i < 10; ++i) {
if (values[i] == 0) {
// Skip over zero elements.
continue;
}
// Print the (nonzero) element.
std::cout << values[i] << ’\n’;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 149
The goto Statement
■ goto statement transfers control to another statement specified by label
■ should generally try to avoid use of goto statement
■ well written code rarely has legitimate use for goto statement
■ example:
int i = 0;
loop: // label for goto statement
do {
if (i == 3) {
++i;
goto loop;
}
std::cout << i << ’\n’;
++i;
} while (i < 10);
■ some restrictions on use of goto (e.g., cannot jump over initialization in
same block as goto)
goto skip; // ERROR
int i = 0;
skip:
++i;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 150
Section 2.3.5
Functions
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 151
Function Parameters, Arguments, and Return Values
■ argument (a.k.a. actual parameter): argument is value supplied to
function by caller; appears in parentheses of function-call operator ⁓⁓⁓⁓⁓⁓
[C++17 §3.2]
■ parameter (a.k.a. formal parameter): parameter is object/reference
declared as part of function that acquires value on entry to function;
appears in function definition/declaration [C++17 §3.16]
⁓⁓⁓⁓⁓⁓⁓
■ although abuse of terminology, parameter and argument often used
interchangeably
■ return value: result passed from function back to caller
■ example:
int square(int i) { // i is parameter
return i * i; // return value is i * i
}
void compute() {
int i = 3;
int j = square(i); // i is argument
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 152
Function Declarations and Definitions
■ function declaration introduces identifier that names function and
specifies following properties of function:
2 number of parameters
2 type of each parameter
2 type of return value (if not automatically deduced)
■ example:
bool isOdd(int); // declare isOdd
bool isOdd(int x); // declare isOdd (x ignored)
■ function definition provides all information included in function
declaration as well as code for body of function
■ example:
bool isOdd(int x) { // declare and define isOdd
return x % 2;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 153
Basic Syntax (Leading Return Type)
■ most basic syntax for function declarations and definitions places return
type at start (i.e., leading return-type syntax)
■ basic syntax for function declaration:
return_type function_name(parameter_declarations);
■ examples of function declarations:
int min(int, int);
double square(double);
■ basic syntax for function definition:
return_type function_name(parameter_declarations)
{
statements
}
■ examples of function definitions:
int min(int x, int y) {return x < y ? x : y;}
double square(double x) {return x * x;}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 154
Trailing Return-Type Syntax
■ with trailing return-type syntax, return type comes after parameter
declarations and auto used as placeholder for where return type would
normally be placed
■ trailing return-type syntax for function declaration:
auto function_name(parameter_declarations) -> return_type;
■ examples of function declarations:
auto min(int, int) -> int;
auto square(double) -> double;
■ trailing return-type syntax for function definition:
auto function_name(parameter_declarations) -> return_type
{
statements
}
■ examples of function definitions:
auto min(int x, int y) -> int
{return x < y ? x : y;}
auto square(double x) -> double {return x * x;}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 155
The return Statement
■ return statement used to exit function, passing specified return value (if
any) back to caller
■ code in function executes until return statement is reached or execution
falls off end of function
■ if function return type is not void, return statement takes single
parameter indicating value to be returned
■ if function return type is void, function does not return any value and
return statement takes either no parameter or expression of type void
■ falling off end of function equivalent to executing return statement with
no value
■ example:
double unit_step(double x) {
if (x >= 0.0) {
return 1.0; // exit with return value 1.0
}
return 0.0; // exit with return value 0.0
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 156
Automatic Return-Type Deduction
■ with both leading and trailing return-type syntax, can specify return type
as auto
■ in this case, return type of function will be automatically deduced
■ if function definition has no return statement, return type deduced to be
void
■ otherwise, return type deduced to match type in expression of return
statement or, if return statement has no expression, as void
■ if multiple return statements, must use same type for all return
expressions
■ when return-type deduction used, function definition must be visible in
order to call function (since return type cannot be determined otherwise)
■ example:
auto square(double x) {
return x * x;
// x * x has type double
// deduced return type is double
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 157
The main Function
■ entry point to program is always function called main
■ has return type of int
■ can be declared to take either no arguments or two arguments as follows
(although other possibilities may also be supported by implementation):
[C++17 §6.6.1/2]
⁓⁓⁓⁓⁓⁓⁓⁓
int main();
int main(int argc, char* argv[]);
■ two-argument variant allows arbitrary number of C-style strings to be
passed to program from environment in which program run
■ argc: number of C-style strings provided to program
■ argv: array of pointers to C-style strings
■ argv[0] is name by which program invoked
■ argv[argc] is guaranteed to be 0 (i.e., null pointer)
■ argv[1], argv[2], . . ., argv[argc - 1] typically correspond to
command line options
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 158
The main Function (Continued)
■ suppose that following command line given to shell:
program one two three
■ main function would be invoked as follows:
int argc = 4;
char* argv[] = {
"program", "one", "two", "three", 0
};
main(argc, argv);
■ return value of main typically passed back to operating system
■ can also use function void exit(int) to terminate program, passing
integer return value back to operating system
■ return statement in main is optional ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §6.6.1/5]
■ if control reaches end of main without encountering return statement,
effect is that of executing “return 0;”
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 159
Lifetime
■ lifetime of object is period of time in which object exists (e.g., block,
function, global)
int x;
void wasteTime()
{
int j = 10000;
while (j > 0) {
--j;
}
for (int i = 0; i < 10000; ++i) {
}
}
■ in above example: x global scope and lifetime; j function scope and
lifetime; i block scope and lifetime
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 160
Parameter Passing
■ function parameter can be passed by value or by reference
■ pass by value: function given copy of object from caller
■ pass by reference: function given reference to object from caller
■ to pass parameter by reference, use reference type for parameter
■ example:
void increment(int& x)
// x is passed by reference
{
++x;
}
double square(double x)
// x is passed by value
{
return x * x;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 161
Pass-By-Value Versus Pass-By-Reference
■ if function needs to change value of object in caller, must pass by
reference
■ for example:
void increment(int& x)
// x refers to object in caller
{
++x;
}
■ if object being passed to function is expensive to copy (e.g., a very large
data type), always faster to pass by reference
■ for example:
double compute(const std::vector<double>& x)
// x refers to object in caller
// object is not copied
{
double result;
// ... (initialize result with value computed from x)
return result;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 162
Increment Example: Incorrectly Using Pass By Value
■ consider code:
1 void increment(int x) {
2 ++x;
3 }
4
5 void func() {
6 int i = 0;
7 increment(i); // i is not modified
8 // i is still 0
9 }
■ when func calls increment, parameter passing copies value of i in func
to local variable x in increment:
i in Copy x in
func increment
Value
0 0
■ when code in increment executes, local variable x is incremented (not i
in func):
i in x in
func increment
0 1
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 163
Increment Example: Correctly Using Pass By Reference
■ consider code:
1 void increment(int& x) {
2 ++x;
3 }
4
5 void func() {
6 int i = 0;
7 increment(i); // i is incremented
8 // i is now 1
9 }
■ when func calls increment, reference x in increment is bound to object
i in func (i.e., x becomes alias for i):
i in func
and
x in increment
0
■ when code in increment executes, x is incremented, which is alias for i
in func:
i in func
and
x in increment
1
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 164
The const Qualifier and Functions
■ const qualifier can be used in function declaration to make promises
about what non-local objects will not be modified by function
■ for function parameter of pointer type, const-ness of pointed-to object (i.e.,
pointee) extremely important
■ if pointee is const, function promises not to change pointee; for example:
int strlen(const char*); // get string length
■ for function parameter of reference type, const-ness of referred-to object
(i.e., referee) extremely important
■ if referee is const, function promises not to change referee; for example:
std::complex<double>
square(const std::complex<double>&);
// compute square of number
■ not making appropriate choice of const-ness for pointed-to or referred-to
object will result in fundamentally incorrect code
■ if function will never modify pointee/referee associated with function
parameter, parameter type should be made pointer/reference to const
object
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 165
String Length Example: Not Const Correct
1 // ERROR: parameter type should be const char*
2 int string_length(char* s) {
3 int n = 0;
4 while (*s++ != ’\0’) {++n;}
5 return n;
6 }
7
8 int main() {
9 char buf[] = "Goodbye";
10 const char* const m1 = "Hello";
11 char* const m2 = &buf[0];
12 int n1 = string_length(m1);
13 // must copy argument m1 to parameter s:
14 // char* s = m1;
15 // convert from const char* const to char*
16 // ERROR: must discard const from pointee
17 int n2 = string_length(m2);
18 // must copy argument m2 to parameter s:
19 // char* s = m2;
20 // convert from char* const to char*
21 // OK: constness of pointee unchanged
22 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 166
String Length Example: Const Correct
1 // OK: pointee is const
2 int string_length(const char* s) {
3 int n = 0;
4 while (*s++ != ’\0’) {++n;}
5 return n;
6 }
7
8 int main() {
9 char buf[] = "Goodbye";
10 const char* const m1 = "Hello";
11 char* const m2 = &buf[0];
12 int n1 = string_length(m1);
13 // must copy argument m1 to parameter s:
14 // const char* s = m1;
15 // convert from const char* const to const char*
16 // OK: constness of pointee unchanged
17 int n2 = string_length(m2);
18 // must copy argument m2 to parameter s:
19 // const char* s = m2;
20 // convert from char* const to const char*
21 // OK: can add const to pointee
22 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 167
Square Example: Not Const Correct
1 #include <complex>
2
3 using Complex = std::complex<long double>;
4
5 // ERROR: parameter type should be reference to const
6 Complex square(Complex& z) {
7 return z * z;
8 }
9
10 int main() {
11 const Complex c1(1.0, 2.0);
12 Complex c2(1.0, 2.0);
13 Complex r1 = square(c1);
14 // must bind parameter z to argument c1
15 // Complex& z = c1;
16 // convert from const Complex to Complex&
17 // ERROR: must discard const from referee
18 Complex r2 = square(c2);
19 // must bind parameter z to argument c2
20 // Complex& z = c2;
21 // convert from Complex to Complex&
22 // OK: constness of referee unchanged
23 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 168
Square Example: Const Correct
1 #include <complex>
2
3 using Complex = std::complex<long double>;
4
5 // OK: parameter type is reference to const
6 Complex square(const Complex& z) {
7 return z * z;
8 }
9
10 int main() {
11 const Complex c1(1.0, 2.0);
12 Complex c2(1.0, 2.0);
13 Complex r1 = square(c1);
14 // must bind parameter z to argument c1
15 // const Complex& z = c1;
16 // convert from const Complex to const Complex&
17 // OK: constness of referee not discarded
18 Complex r2 = square(c2);
19 // must bind parameter z to argument c2
20 // const Complex& z = c2;
21 // convert from Complex to const Complex&
22 // OK: can add const to referee
23 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 169
Function Types and the const Qualifier
1 // top-level qualifiers of parameter types are
2 // not part of function type and should be omitted
3 // from function declaration
4
5 // BAD: const not part of function type
6 // (nothing here to which const can refer)
7 bool is_even(const unsigned int);
8
9 // OK
10 bool is_odd(unsigned int);
11
12 // OK: parameter with top-level const qualifier
13 // is ok in function definition
14 bool is_even(const unsigned int x) {
15 // cannot change x in function
16 return x % 2 == 0;
17 }
18
19 // OK
20 bool is_odd(unsigned int x) {
21 // x can be changed if desired
22 return x % 2 != 0;
23 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 170
Inline Functions
■ in general programming sense, inline function is function for which
compiler copies code from function definition directly into code of calling
function rather than creating separate set of instructions in memory
■ since code copied directly into calling function, no need to transfer control
to separate piece of code and back again to caller, eliminating
performance overhead of function call
■ inline typically used for very short functions (where overhead of calling
function is large relative to cost of executing code within function itself)
■ can request function be made inline by including inline qualifier along
with function return type (but compiler may ignore request)
■ inline function must be defined in each translation unit in which function is
used and all definitions must be identical; this is exception to
one-definition rule ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §10.1.6/6]
■ example:
inline bool isEven(int x) {
return x % 2 == 0;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 171
Inlining of a Function
■ inlining of isEven function transforms code fragment 1 into code
fragment 2
■ Code fragment 1:
inline bool isEven(int x) {
return x % 2 == 0;
}
void myFunction() {
int i = 3;
bool result = isEven(i);
}
■ Code fragment 2:
void myFunction() {
int i = 3;
bool result = (i % 2 == 0);
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 172
The constexpr Qualifier for Functions
■ constexpr qualifier indicates return value of function is constant
expression (i.e., can be evaluated at compile time) provided that all
arguments to function are constant expressions
■ constexpr function required to be evaluated at compile time if all
arguments are constant expressions and return value used in constant
expression
■ constexpr functions are implicitly inline ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §10.1.5/1]
■ constexpr function very restricted in what it can do (e.g., no external state,
can only call constexpr functions)
■ example:
constexpr int factorial(int n)
{return n >= 2 ? (n * factorial(n - 1)) : 1;}
int u[factorial(5)];
// OK: factorial(5) is constant expression
int x = 5;
int v[factorial(x)];
// ERROR: factorial(x) is not constant
// expression
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 173
Constexpr Example
1 constexpr int square(int i) {
2 return i * i;
3 }
4
5 constexpr int func(int n) {
6 int sum = 0;
7 for (int i = 1; i <= n; ++i) {
8 sum += square(i);
9 }
10 return sum;
11 }
12
13 int main() {
14 // at compile time, compute sum of the squares of
15 // 1, 2, 3 (i.e., 14)
16 constexpr int result = func(3);
17 static_assert(result == 14);
18 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 174
Constexpr Function Example: square
1 #include <iostream>
2
3 constexpr double square(double x) {
4 return x * x;
5 }
6
7 int main() {
8 constexpr double a = square(2.0);
9 // must be computed at compile time
10
11 double b = square(0.5);
12 // might be computed at compile time
13
14 double t;
15 if (!(std::cin >> t)) {
16 return 1;
17 }
18 const double c = square(t);
19 // must be computed at run time
20
21 std::cout << a << ’ ’ << b << ’ ’ << c << ’\n’;
22 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 175
Constexpr Function Example: power_int (Recursive)
1 #include <iostream>
2
3 constexpr double power_int_helper(double x, int n) {
4 return (n > 0) ? x * power_int_helper(x, n - 1) : 1;
5 }
6
7 constexpr double power_int(double x, int n) {
8 return (n < 0) ? power_int_helper(1.0 / x, -n) :
9 power_int_helper(x, n);
10 }
11
12 int main() {
13 constexpr double a = power_int(0.5, 8);
14 // must be computed at compile time
15
16 double b = power_int(0.5, 8);
17 // might be computed at compile time
18
19 double x;
20 if (!(std::cin >> x)) {return 1;}
21 const double c = power_int(x, 2);
22 // must be computed at run time
23
24 std::cout << a << ’ ’ << b << ’ ’ << c << ’\n’;
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 176
Constexpr Function Example: power_int (Iterative)
1 #include <iostream>
2
3 constexpr double power_int(double x, int n) {
4 double result = 1.0;
5 if (n < 0) {
6 x = 1.0 / x;
7 n = -n;
8 }
9 while (--n >= 0) {
10 result *= x;
11 }
12 return result;
13 }
14
15 int main() {
16 constexpr double a = power_int(0.5, 8);
17 // must be computed at compile time
18
19 double b = power_int(0.5, 8);
20 // might be computed at compile time
21
22 double x;
23 if (!(std::cin >> x)) {return 1;}
24 const double c = power_int(x, 2);
25 // must be computed at run time
26
27 std::cout << a << ’ ’ << b << ’ ’ << c << ’\n’;
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 177
The consteval Specifier
■ immediate function is function that must always yield constant
expression
■ in other words, immediate function must execute at compile time
■ consteval qualifier can added to declaration of function to assert that
function is immediate function
■ consteval function must satisfy all requirements of constexpr function
■ consteval implies inline
■ cannot use constexpr or constinit qualifiers with consteval
(as consteval implies constexpr and constinit)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 178
Immediate-Function Example
1 constexpr double square(double x) {return x * x;}
2
3 consteval double func(double x) {
4 return 2.0 * square(x);
5 }
6
7 int main() {
8 constexpr double cx = func(2.0);
9 // OK: func evaluated at compile time
10 double x = func(2.0);
11 // OK: func evaluated at compile-time
12 double y1 = func(cx);
13 // OK: func evaluated at compile-time
14 // double y2 = func(x);
15 // ERROR: x not constexpr
16 double y3 = square(x);
17 // OK: square can be evaluated at run-time
18 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 179
The std::is_constant_evaluated Function
■ std::is_constant_evaluated is (constexpr) function that returns
boolean value indicating if called from within constant-evaluated context
(i.e., code executing at compile time)
■ being able to detect whether code running at compile-time can be very
useful
■ for example, can have constexpr function use different algorithm
depending on whether invoked at compile-time or run-time
■ note that is_constant_evaluated always returns true in condition for
constexpr if (since constexpr-if always evaluated at compile time)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 180
Example: Distinct Algorithms for Compile- and Run-Times
1 #include <cassert>
2 #include <cmath>
3 #include <type_traits>
4
5 constexpr double square_root(double x) {
6 if (std::is_constant_evaluated() && x >= 0.0) {
7 double cur = 0.5 * x;
8 double old = 0.0;
9 while (cur != old) {
10 old = cur;
11 cur = 0.5 * (old + x / old);
12 }
13 return cur;
14 } else {return std::sqrt(x);}
15 }
16
17 int main() {
18 constexpr double x = 1.414213562373095;
19 constexpr double cy = x * x;
20 double y = cy;
21 constexpr double z1 = square_root(cy);
22 // uses compile-time square-root algorithm
23 double z2 = square_root(cy); // may use std::sqrt
24 double z3 = square_root(y); // uses std::sqrt
25 static_assert(std::abs(z1 - x) < 1e-6);
26 assert(std::abs(z2 - x) < 1e-6);
27 assert(std::abs(z3 - x) < 1e-6);
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 181
Example: Misuse of is_constant_evaluated
1 #include <type_traits>
2 #include <cassert>
3
4 constexpr int func1(int i) {
5 // LIKELY ERROR:
6 // following line of code same as "if (true) {return 42;}"
7 // due to constexpr-if, std::is_constant_evaluated() always true
8 if constexpr(std::is_constant_evaluated()) {return 42;}
9 else {return i;}
10 }
11
12 constexpr int func2(int i) {
13 if (std::is_constant_evaluated()) {return 42;}
14 else {return i;}
15 }
16
17 int main(){
18 constexpr int x = 0;
19 int y = x;
20 static_assert(func1(x) == 42);
21 static_assert(func2(x) == 42);
22 assert(func1(y) == 42); // OK, BUT LIKELY NOT INTENDED
23 assert(func2(y) == 0);
24 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 182
Const Variables in Constant Expressions
■ in some very special cases (for largely historical reasons), const variables
usable in constant expressions
■ const variable of integral or enumerated type usable in constant
expression if initializer is constant expression [C++20 §7.7/3–4]
⁓⁓⁓⁓⁓⁓⁓⁓
■ example:
1 constexpr int forty_two() {return 42;}
2 consteval bool is_even(int x) {return !(x % 2);}
3 consteval double cube(double x) {return x * x * x;}
4
5 int main() {
6 const int i = forty_two();
7 /* i is const, has integral type, and is initialized with
8 constant expression; so i usable in constant expression */
9 float x[i]; // OK
10 constexpr bool b = is_even(i); // OK
11 const double d = 42.0;
12 // d not usable in constant expression
13 // double d2 = cube(d); // ERROR: d not constexpr
14 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 183
Example: Const Variables and is_constant_evaluated
1 #include <cassert>
2 #include <type_traits>
3
4 // f returns i at compile time and -i at run time
5 constexpr int f(int i) {
6 const int si = std::is_constant_evaluated() ? 1 : -1;
7 static_assert(si == 1);
8 int s = std::is_constant_evaluated() ? 1 : -1;
9 return s * i;
10 }
11
12 int main() {
13 const int i1 = std::is_constant_evaluated() ? 1 : -1;
14 static_assert(i1 == 1); // i1 evaluated at compile time
15 const int i5 = f(i1);
16 static_assert(i5 == 1); // i5 evaluated at compile time
17 int i2 = std::is_constant_evaluated() ? 1 : -1;
18 assert(i2 == -1); // i2 evaluated at run time
19 const double d1 = std::is_constant_evaluated() ? 1.0 : -1.0;
20 assert(d1 == -1.0); // d1 evaluated at run time
21 constexpr double d2 = std::is_constant_evaluated() ? 1.0 : -1.0;
22 static_assert(d2 == 1.0); // d2 evaluated at compile time
23 constexpr int i3 = f(42); // f evaluated at compile time
24 static_assert(i3 == 42);
25 int i = 42;
26 int i4 = f(i); // f evaluated at run time
27 assert(i4 == -42);
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 184
Compile-Time Versus Run-Time Computation
■ constexpr variables and constexpr functions provide mechanism for
moving computation from run time to compile time
■ benefits of compile-time computation include:
1 no execution-time cost at run-time
2 can facilitate compiler optimization (e.g., eliminate conditional branch if
condition always true/false)
3 can reduce code size since code used only for compile-time computation
does not need to be included in executable
4 can find errors at compile-time and link-time instead of at run time
5 no concerns about order of initialization (which is not necessarily true for
const objects)
6 no synchronization concerns (e.g., multiple threads trying to initialize object)
■ when floating point is involved, compile-time and run-time computations
can yield different results, due to differences in such things as
2 rounding mode in effect
2 processor architecture used for computation (when cross compiling)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 185
Function Overloading
■ function overloading: multiple functions can have same name as long as
they differ in number/type of their arguments
■ example:
void print(int x) {
std::cout << "int has value " << x << ’\n’;
}
void print(double x) {
std::cout << "double has value " << x << ’\n’;
}
void demo() {
int i = 5;
double d = 1.414;
print(i); // calls print(int)
print(d); // calls print(double)
print(42); // calls print(int)
print(3.14); // calls print(double)
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 186
Default Arguments
■ can specify default values for arguments to functions
■ example:
// Compute log base b of x.
double logarithm(double x, double b) {
return std::log(x) / std::log(b);
}
// Declaration of logarithm with a default argument.
double logarithm(double, double = 10.0);
void demo() {
double x =
logarithm(100.0); // calls logarithm(100.0, 10.0)
double y =
logarithm(4.0, 2.0); // calls logarithm(4.0, 2.0)
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 187
Argument Matching
■ call of given function name chooses function that best matches actual
arguments
■ consider all functions in scope for which set of conversions exists so
function could possibly be called
■ best match is intersection of sets of functions that best match on each
argument
■ matches attempted in following order: [C++17 §16.3.3.1]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
1 exact match with zero or more trivial conversions (e.g., T to T&, T& to T,
adding const and/or volatile); of these, those that do not add const
and/or volatile to pointer/reference better than those that do
2 match with promotions (e.g., int to long, float to double)
3 match with standard conversions (e.g., float to int, double to int)
4 match with user-defined conversions
5 match with ellipsis
■ if set of best matches contains exactly one element, this element chosen
as function to call
■ if set of best matches is either empty or contains more than one element,
function call is invalid (since either no matches found or multiple
equally-good matches found)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 188
Argument Matching: Example
1 int max(int, int);
2 double max(double, double);
3
4 int main() {
5 int i, j, k;
6 double a, b, c;
7 // ...
8 k = max(i, j);
9 // viable functions: max(int, int), max(double, double)
10 // best match on first argument: max(int, int)
11 // best match on second argument: max(int, int)
12 // best viable function: max(int, int)
13 // OK: calls max(int, int)
14 c = max(a, b);
15 // viable functions: max(int, int), max(double, double)
16 // best match on first argument: max(double, double)
17 // best match on second argument: max(double, double)
18 // best viable function: max(double, double)
19 // OK: calls max(double, double)
20 c = max(i, b);
21 // viable functions: max(int, int), max(double, double)
22 // best match on first argument: max(int, int)
23 // best match on second argument: max(double, double)
24 // no best viable function
25 // ERROR: ambiguous function call
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 189
The assert Macro
■ assert macro allows testing of boolean condition at run time
■ typically used to test sanity of code (e.g., test preconditions,
postconditions, or other invariants) or test validity of assumptions made by
code
■ defined in header file cassert
■ macro takes single argument: boolean expression
■ if assertion fails, program is terminated by calling std::abort
■ if NDEBUG preprocessor symbol is defined at time cassert header file
included, all assertions are disabled (i.e., not checked)
■ assert(expr) is constant expression if expr is constant expression that
evaluates to true or NDEBUG is defined ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §22.3.2/1]
■ example:
#include <cassert>
double sqrt(double x) {
assert(x >= 0);
// ...
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 190
Section 2.3.6
Input/Output (I/O)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 191
Basic I/O
■ relevant declarations and such in header file iostream
■ std::istream: stream from which characters/data can be read (i.e.,
input stream)
■ std::ostream: stream to which characters/data can be written (i.e.,
output stream)
■ std::istream std::cin standard input stream
■ std::ostream std::cout standard output stream
■ std::ostream std::cerr standard error stream
■ in most environments, above three streams refer to user’s terminal by
default
■ output operator (inserter) <<
■ input operator (extractor) >>
■ stream can be used as bool expression; converts to true if stream has
not encountered any errors and false otherwise (e.g., if invalid data
read or I/O error occurred)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 192
Basic I/O Example
1 #include <iostream>
2
3 int main() {
4 std::cout << "Enter an integer: ";
5 int x;
6 std::cin >> x;
7 if (std::cin) {
8 std::cout << "The integer entered was "
9 << x << ".\n";
10 } else {
11 std::cerr <<
12 "End-of-file reached or I/O error.\n";
13 }
14 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 193
I/O Manipulators
■ manipulators provide way to control formatting of data values written to
streams as well as parsing of data values read from streams
■ declarations related information for manipulators can be found in header
files: ios, iomanip, istream, and ostream
■ most manipulators used to control output formatting
■ focus here on manipulators as they pertain to output
■ manipulator may have immediate effect (e.g., endl), only affect next data
value output (e.g., setw), or affect all subsequent data values output (e.g.,
setprecision)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 194
I/O Manipulators (Continued)
Name Description
setw set field width
setfill set fill character
endl insert newline and flush
flush flush stream
dec use decimal
hex use hexadecimal
oct use octal
showpos show positive sign
noshowpos do not show positive sign
left left align
right right align
fixed write floating-point values in fixed-point notation
scientific write floating-point values in scientific notation
setprecision for default notation, specify maximum number of mean-
ingful digits to display before and after decimal point; for
fixed and scientific notations, specify exactly how many
digits to display after decimal point (padding with trail-
ing zeros if necessary)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 195
I/O Manipulators Example
1 #include <iostream>
2 #include <ios>
3 #include <iomanip>
4
5 int main() {
6 constexpr double pi = 3.1415926535;
7 constexpr double big = 123456789.0;
8 // default notation
9 std::cout << pi << ’ ’ << big << ’\n’;
10 // fixed-point notation
11 std::cout << std::fixed << pi << ’ ’ << big << ’\n’;
12 // scientific notation
13 std::cout << std::scientific << pi << ’ ’ << big << ’\n’;
14 // fixed-point notation with 7 digits after decimal point
15 std::cout << std::fixed << std::setprecision(7) << pi << ’ ’
16 << big << ’\n’;
17 // fixed-point notation with precision and width specified
18 std::cout << std::setw(8) << std::fixed << std::setprecision(2)
19 << pi << ’ ’ << std::setw(20) << big << ’\n’;
20 // fixed-point notation with precision, width, and fill specified
21 std::cout << std::setw(8) << std::setfill(’x’) << std::fixed
22 << std::setprecision(2) << pi << ’ ’ << std::setw(20) << big << ’\n’;
23 }
24
25 /* This program produces the following output:
26 3.14159 1.23457e+08
27 3.141593 123456789.000000
28 3.141593e+00 1.234568e+08
29 3.1415927 123456789.0000000
30 3.14 123456789.00
31 xxxx3.14 xxxxxxxx123456789.00
32 */
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 196
Use of std::istream::eof
■ do not use std::istream::eof to determine if earlier input operation
has failed, as this will not always work
■ eof simply returns end-of-file (EOF) flag for stream
■ EOF flag for stream can be set during successful input operation (when
input operation takes places just before end of file)
■ when stream extractors (i.e., operator>>) used, fields normally
delimited by whitespace
■ to read all data in whitespace-delimited field, must read one character
beyond field in order to know that end of field has been reached
■ if field followed immediately by EOF without any intervening whitespace
characters, reading one character beyond field will cause EOF to be
encountered and EOF bit for stream to be set
■ in preceding case, however, EOF being set does not mean that input
operation failed, only that stream data ended immediately after field that
was read
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 197
Example: Incorrect Use of eof
■ example of incorrect use of eof:
1 #include <iostream>
2
3 int main() {
4 while (true) {
5 int x;
6 std::cin >> x;
7 // std::cin may not be in a failed state.
8 if (std::cin.eof()) {
9 // Above input operation may have succeeded.
10 std::cout << "EOF encountered\n";
11 break;
12 }
13 std::cout << x << ’\n’;
14 }
15 }
■ code incorrectly assumes that eof will only return true if preceding input
operation has failed
■ last field in stream will be incorrectly ignored if not followed by at least one
whitespace character; for example, if input stream consists of three
character sequence ’1’, space, ’2’, program will output:
1
EOF encountered
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 198
Example: Correct Use of eof
■ to determine if input operation failed, simply check if stream in failed state
■ if stream already known to be in failed state and need to determine
specifically if failure due to EOF being encountered, then use eof
■ example of correct use of eof:
1 #include <iostream>
2
3 int main() {
4 int x;
5 // Loop while std::cin not in a failed state.
6 while (std::cin >> x) {
7 std::cout << x << ’\n’;
8 }
9 // Now std::cin must be in a failed state.
10 // Use eof to determine the specific reason
11 // for failure.
12 if (std::cin.eof()) {
13 std::cout << "EOF encountered\n";
14 } else {
15 std::cout << "input error (excluding EOF)\n";
16 }
17 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 199
Use of std::endl
■ std::endl is not some kind of string constant
■ std::endl is stream manipulator and declared as
std::ostream& std::endl(std::ostream&)
■ inserting endl to stream always (regardless of operating system)
equivalent to outputting single newline character ’\n’ followed by flushing
stream
■ flushing of stream can incur very substantial overhead; so only flush when
strictly necessary
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 200
Use of std::endl (Continued)
■ some operating systems terminate lines with single linefeed character
(i.e., ’\n’), while other operating systems use carriage-return and
linefeed pair (i.e., ’\r’ plus ’\n’)
■ existence of endl has nothing to do with dealing with handling new lines
in operating-system independent manner
■ when stream opened in text mode, translation between newline characters
and whatever character(s) operating system uses to terminate lines is
performed automatically (both for input and output)
■ above translation done for all characters input and output and has nothing
to do with endl
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 201
Stream Extraction Failure
■ for built-in types, if stream extraction fails, value of target for stream
extraction depends on reason for failure ⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §25.4.2.1.2/3 (Stage 3)]
■ in following example, what is value of x if stream extraction fails:
int x;
std::cin >> x;
if (!std::cin) {
// what is value of x?
}
■ in above example, x may be uninitialized upon stream extraction failure
■ if failure due to I/O error or EOF, target of extraction is not modified
■ if failure due to badly formatted data, target of extraction is zero
■ if failure due to overflow, target of extraction is closest
machine-representable value
■ common error: incorrectly assume that target of extraction will always be
initialized if extraction fails
■ for class types, also dangerous to assume target of extraction always
written upon failure
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 202
Stream Extraction Failure (Continued)
1 #include <iostream>
2 #include <sstream>
3 #include <limits>
4 #include <cassert>
5
6 int main() {
7 int x;
8
9 std::stringstream s0("");
10 x = -1;
11 s0 >> x;
12 // No data; x is not set by extraction.
13 assert(s0.fail() && x == -1);
14
15 std::stringstream s1("A");
16 x = -1;
17 s1 >> x;
18 // Badly formatted data; x is zeroed.
19 assert(s1.fail() && x == 0);
20
21 std::stringstream
22 s2("9999999999999999999999999999999999999999");
23 x = -1;
24 s2 >> x;
25 // Overflow; x set to closest machine-representable value.
26 assert(s2.fail() && x == std::numeric_limits<int>::max());
27 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 203
Testing Failure State of Streams
■ consider istream or ostream object s
■ !s is equivalent to s.fail()
■ bool(s) is not equivalent to s.good() [C++17 §30.5.5.4/1] [C++17 §30.5.5.4/7]
⁓⁓⁓⁓⁓⁓⁓⁓⁓ ⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ s.good() is not the same as !s.fail()
■ do not use good as opposite of fail since this is wrong
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 204
Section 2.3.7
Miscellany
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 205
Namespaces
■ namespace is region that provides scope for identifiers declared inside
■ namespace provides mechanism for reducing likelihood of naming
conflicts
■ syntax for namespace has general form:
namespace name {
body
}
■ name: identifier that names namespace
■ body: body of namespace (i.e., code)
■ all identifiers (e.g., names of variables, functions, and types) declared in
body made to belong to scope associated with namespace name
■ same identifier can be re-used in different namespaces, since each
namespace is separate scope
■ scope-resolution operator (i.e., ::) can be used to explicitly specify
namespace to which particular identifier belongs
■ using statement can be used to bring identifiers from other namespaces
into current scope
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 206
Namespaces: Example
1 #include <iostream>
2
3 using std::cout; // bring std::cout into current scope
4
5 namespace mike {
6 int someValue;
7 void initialize() {
8 cout << "mike::initialize called\n";
9 someValue = 0;
10 }
11 }
12
13 namespace fred {
14 double someValue;
15 void initialize() {
16 cout << "fred::initialize called\n";
17 someValue = 1.0;
18 }
19 }
20
21 void func() {
22 mike::initialize(); // call initialize in namespace mike
23 fred::initialize(); // call initialize in namespace fred
24 using mike::initialize;
25 // bring mike::initialize into current scope
26 initialize(); // call mike::initialize
27 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 207
Nested Namespace Definitions
■ name given in namespace declaration can be qualified name in order to
succinctly specify nested namespace
■ consider following namespace declaration:
namespace foo {
namespace bar {
namespace impl {
// ...
}
}
}
■ preceding declaration can be written more succinctly as:
namespace foo::bar::impl {
// ...
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 208
Namespace Aliases
■ identifier can be introduced as alias for namespace
■ syntax has following form:
namespace alias_name = ns_name;
■ identifier alias_name is alias for namespace ns_name
■ namespace aliases particularly useful for creating short names for
deeply-nested namespaces or namespaces with long names
■ example:
1 #include <iostream>
2
3 namespace foobar {
4 namespace miscellany {
5 namespace experimental {
6 int get_meaning_of_life() {return 42;}
7 void greet() {std::cout << "hello\n";};
8 }
9 }
10 }
11
12 int main() {
13 namespace n = foobar::miscellany::experimental;
14 n::greet();
15 std::cout << n::get_meaning_of_life() << ’\n’;
16 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 209
Inline Namespaces
■ namespace can be made inline, in which case all identifiers in namespace
also visible in enclosing namespace
■ inline namespaces useful, for example, for library versioning
■ example:
1 #include <cassert>
2
3 // some awesome library
4 namespace awesome {
5 // version 1
6 namespace v1 {
7 int meaning_of_life() {return 41;}
8 }
9 // new and improved version 2
10 // which should be default for library users
11 inline namespace v2 {
12 int meaning_of_life() {return 42;}
13 }
14 }
15
16 int main() {
17 assert(awesome::v1::meaning_of_life() == 41);
18 assert(awesome::v2::meaning_of_life() == 42);
19 assert(awesome::meaning_of_life() == 42);
20 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 210
Unnamed Namespaces
■ can create unnamed namespace (i.e., namespace without name)
■ unnamed namespace often referred to as anonymous namespace
■ each translation unit may contain its own unique unnamed namespace
■ entities defined in unnamed namespace only visible in its associated
translation unit (i.e., has internal linkage)
■ example:
1 #include <iostream>
2
3 namespace {
4 const int forty_two = 42;
5 int x;
6 }
7
8 int main() {
9 x = forty_two;
10 std::cout << x << ’\n’;
11 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 211
Memory Allocation: new and delete
■ to allocate memory, use new statement
■ to deallocate memory allocated with new statement, use delete
statement
■ similar to malloc and free in C
■ two forms of allocation: 1) single object (i.e., nonarray case) and 2) array
of objects
■ array version of new/delete distinguished by []
■ example:
char* buffer = new char[64]; // allocate
// array of 64 chars
delete [] buffer; // deallocate array
double* x = new double; // allocate single double
delete x; // deallocate single object
■ important to match nonarray and array versions of new and delete:
char* buffer = new char[64]; // allocate
delete buffer; // ERROR: nonarray delete to
// delete array
// may compile fine, but crash
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 212
User-Defined Literals
■ C++ has several categories of literals (e.g., character, integer,
floating-point, string, boolean, and pointer)
■ can define additional literals based on these categories
■ identifier used as suffix for user-defined literal must begin with underscore
■ suffixes that do not begin with underscore are reserved for use by
standard library
■ example:
1 #include <iostream>
2 #include <complex>
3
4 std::complex<long double> operator "" _i(long double d) {
5 return std::complex<long double>(0.0, d);
6 }
7
8 int main() {
9 auto z = 3.14_i;
10 std::cout << z << ’\n’;
11 }
12
13 // Program output:
14 // (0,3.14)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 213
Attributes
■ attributes provide unified syntax for implementation-defined language
extensions
■ attribute can be used almost anywhere in source code and can be applied
to almost anything (e.g., types, variables, functions, names, code blocks,
and translation units)
■ specific types of entities to which attribute can be applied depends on
particular attribute in question
■ attribute specifiers start with two consecutive left brackets and continue to
two consecutive right brackets
■ example:
[[deprecated]]
void some_very_old_function() {/* ... */};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 214
Some Standard Attributes
Name Description
noreturn function does not return
deprecated use of entity is deprecated (i.e., allowed but
discouraged)
fallthrough fall through in switch statement is deliberate
maybe_unused entity (e.g., variable) may be unused
nodiscard used to indicate that return value of function
should not be ignored
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 215
Some GCC and Clang Attributes
GCC C++ Compiler
Name Description
gnu::noinline do not inline function
gnu::no_sanitize_address do not instrument function for address
sanitizer
gnu::no_sanitize_undefined do not instrument function for undefined-
behavior sanitizer
Clang C++ Compiler
Name Description
gnu::noinline do not inline function
clang::no_sanitize do not instrument function for sanitizer
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 216
Section 2.3.8
References
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 217
References I
1 D. Saks. Placing const in declarations. Embedded Systems
Programming, pages 19–20, June 1998.
2 D. Saks. What const really means. Embedded Systems Programming,
pages 11–14, Aug. 1998.
3 D. Saks. const T vs. T const. Embedded Systems Programming, pages
13–16, Feb. 1999.
4 D. Saks. Top-level cv-qualifiers in function parameters. Embedded
Systems Programming, pages 63–65, Feb. 2000.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 218
Section 2.4
Classes
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 219
Classes
■ since fundamental types provided by language are quite limiting, language
provides mechanism for defining new (i.e., user-defined) types
■ class is user-defined type
■ class specifies:
1 how objects of class are represented
2 operations that can be performed on objects of class
■ not all parts of class are directly accessible to all code
■ interface is part of class that is directly accessible to its users
■ implementation is part of class that its users access only indirectly
through interface
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 220
Section 2.4.1
Members and Access Specifiers
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 221
Class Members
■ class consists of zero or more members
■ three basic kinds of members (excluding enumerators):
1 data member
2 function member
3 type member
■ data members define representation of class object
■ function members (also called member functions) provide operations on
such objects
■ type members specify any types associated with class
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 222
Access Specifiers
■ can control level of access that users of class have to its members
■ three levels of access:
1 public
2 protected
3 private
■ public: member can be accessed by any code
■ private: member can only be accessed by other members of class and
friends of class (to be discussed shortly)
■ protected: relates to inheritance (discussion deferred until later)
■ public members constitute class interface
■ private members constitute class implementation
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 223
Class Example
■ class typically has form:
class Widget // The class is named Widget.
{
public:
// public members
// (i.e., the interface to users)
// usually functions and types (but not data)
private:
// private members
// (i.e., the implementation details only
// accessible by members of class)
// usually functions, types, and data
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 224
Default Member Access
■ class members are private by default
■ two code examples below are exactly equivalent:
class Widget {
// ...
};
class Widget {
private:
// ...
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 225
The struct Keyword
■ struct is class where members public by default
■ two code examples below are exactly equivalent:
struct Widget {
// ...
};
class Widget {
public:
// ...
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 226
Data Members
■ class example:
class Vector_2 { // Two-dimensional vector class.
public:
double x; // The x component of the vector.
double y; // The y component of the vector.
};
void func() {
Vector_2 v;
v.x = 1.0; // Set data member x to 1.0
v.y = 2.0; // Set data member y to 2.0
}
■ above class has data members x and y
■ members accessed by member-selection operator (i.e., “.”)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 227
Function Members
■ class example:
class Vector_2 { // Two-dimensional vector class.
public:
void initialize(double newX, double newY);
double x; // The x component of the vector.
double y; // The y component of the vector.
};
void Vector_2::initialize(double newX, double newY) {
x = newX; // "x" means "this->x"
y = newY; // "y" means "this->y"
}
void func() {
Vector_2 v; // Create Vector_2 called v.
v.initialize(1.0, 2.0); // Initialize v to (1.0, 2.0).
}
■ above class has member function initialize
■ to refer to member of class outside of class body must use
scope-resolution operator (i.e., ::)
■ for example, in case of initialize function, we use
Vector_2::initialize
■ member function always has implicit parameter referring to class object
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 228
The this Keyword
■ member function always has implicit parameter referring to class object
■ implicit parameter accessible inside member function via this keyword
■ this is pointer to object for which member function is being invoked
■ data members can be accessed through this pointer
■ since data members can also be referred to directly by their names,
explicit use of this often not needed and normally avoided
■ example:
class Widget {
public:
int updateValue(int newValue) {
int oldValue = value; // "value" means "this->value"
value = newValue; // "value" means "this->value"
return oldValue;
}
private:
int value;
};
void func() {
Widget x;
x.updateValue(5);
// in Widget::updateValue, variable this equals &x
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 229
const Member Functions
■ member function has reference to object of class as implicit parameter
(i.e., object pointed to by this)
■ need way to indicate if member function can change value of object
■ const member function cannot change value of object
1 class Counter {
2 public:
3 int getCount() const
4 {return count;} // count means this->count
5 void setCount(int newCount)
6 {count = newCount;} // count means this->count
7 void incrementCount()
8 {++count;} // count means this->count
9 private:
10 int count; // counter value
11 };
12
13 void func() {
14 Counter ctr;
15 ctr.setCount(0);
16 int count = ctr.getCount();
17 const Counter& ctr2 = ctr;
18 count = ctr2.getCount(); // getCount better be const!
19 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 230
Definition of Function Members in Class Body
■ member function whose definition is provided in body of class is implicitly
inline (except when in module purview)
■ two code examples below are exactly equivalent:
class MyInteger {
public:
// Set the value of the integer and return the old value.
int setValue(int newValue) {
int oldValue = value;
value = newValue;
return oldValue;
}
private:
int value;
};
class MyInteger {
public:
// Set the value of the integer and return the old value.
int setValue(int newValue);
private:
int value;
};
inline int MyInteger::setValue(int newValue) {
int oldValue = value;
value = newValue;
return oldValue;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 231
Type Members
■ example:
class Point_2 { // Two-dimensional point class.
public:
using Coordinate = double; // Coordinate type.
Coordinate x; // The x coordinate of the point.
Coordinate y; // The y coordinate of the point.
};
void func() {
Point_2 p;
// ...
Point_2::Coordinate x = p.x;
// Point_2::Coordinate same as double
}
■ above class has type member Coordinate
■ to refer to type member outside of class body, we must use
scope-resolution operator (i.e., ::)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 232
Friends
■ normally, only class has access to its private members
■ sometimes, necessary to allow another class or function to have access to
private members of class
■ friend of class is function/class that is allowed to access private members
of class
■ to make function or class friend of another class, use friend statement
■ example:
class Gadget; // forward declaration of Gadget
class Widget {
// ...
friend void myFunc();
// function myFunc is friend of Widget
friend class Gadget;
// class Gadget is friend of Widget
// ...
};
■ generally, use of friends should be avoided except when absolutely
necessary
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 233
Class Example
1 class Widget {
2 public:
3 int setValue(int newValue) { // member function
4 int oldValue = value; // save old value
5 value = newValue; // change value to new value
6 return oldValue; // return old value
7 }
8 private:
9 friend void wasteTime();
10 void doNothing() {}
11 int value; // data member
12 };
13
14 void wasteTime() {
15 Widget x;
16 x.doNothing(); // OK: friend
17 x.value = 5; // OK: friend
18 }
19
20 void func() {
21 Widget x; // x is object of type Widget
22 x.setValue(5); // call Widget’s setValue member
23 // sets x.value to 5
24 x.value = 5; // ERROR: value is private
25 x.doNothing(); // ERROR: doNothing is private
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 234
Section 2.4.2
Constructors and Destructors
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 235
Propagating Values: Copying and Moving
■ Suppose that we have two objects of the same type and we want to
propagate the value of one object (i.e., the source) to the other object (i.e.,
the destination).
■ This can be accomplished in one of two ways: 1) copying or 2) moving.
■ Copying propagates the value of the source object to the destination
object without modifying the source object.
■ Moving propagates the value of the source object to the destination
object and is permitted to modify the source object.
■ Moving is always at least as efficient as copying, and for many types,
moving is more efficient than copying.
■ For some types, copying does not make sense, while moving does (e.g.,
std::ostream, std::istream).
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 236
Copying and Moving
■ Copy operation. Propagating the value of the source object source to the
destination object destination by copying.
source destination source destination
a b a a
Before Copy After Copy
■ A copy operation does not modify the value of the source object.
■ Move operation. Propagating the value of the source object source to
the destination object destination by moving.
source destination source destination
a b ? a
Before Move After Move
■ A move operation is not guaranteed to preserve the value of the source
object. After the move operation, the source object has a value that is
valid but typically unspecified.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 237
Constructors
■ when new object created usually desirable to immediately initialize it to
some known state
■ prevents object from accidentally being used before it is initialized
■ constructor is member function that is called automatically when object
created in order to initialize its value
■ constructor has same name as class (i.e., constructor for class T is
function T::T)
■ constructor has no return type (not even void)
■ constructor cannot be called directly (although placement new provides
mechanism for achieving similar effect, in rare cases when needed)
■ constructor can be overloaded
■ before constructor body is entered, all data members of class type are first
constructed in order of declaration in class definition
■ in certain circumstances, constructors may be automatically provided
■ sometimes, automatically provided constructors will not have correct
behavior
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 238
Default Constructor
■ constructor that can be called with no arguments known as default
constructor ⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §15.1/4]
■ example:
class Vector { // Two-dimensional vector class.
public:
Vector() // Default constructor.
{x_ = 0.0; y_ = 0.0;}
// ...
private:
double x_; // The x component of the vector.
double y_; // The y component of the vector.
};
Vector v; // calls Vector::Vector(); v set to (0,0)
Vector x(); // declares function x that returns Vector
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 239
Defaulted Default Constructor
■ defaulted default constructor for class T performs similar initialization as
constructor defined as [C++17 §15.1/7]
⁓⁓⁓⁓⁓⁓⁓⁓
T::T() {}
■ if class has no default member initializers, this corresponds to default
constructing each data member of class type and leaving data members
of built-in type uninitialized
■ defaulted default constructor automatically provided (i.e., implicitly
declared) as public member if no user-declared constructors ⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §15.1/4]
■ example:
#include <string>
// class has implicitly-defined defaulted
// default constructor
struct Widget {
void foo() {}
std::string s;
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 240
Copy Constructor
■ for class T, constructor taking lvalue reference to T as first parameter that
can be called with one argument known as copy constructor ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §15.8.1/1]
■ used to create object by copying from already-existing object
■ copy constructor for class T typically is of form T(const T&)
■ example:
class Vector { // Two-dimensional vector class.
public:
Vector() {x_ = 0.0; y_ = 0.0;} // Default constructor
Vector(const Vector& v) // Copy constructor.
{x_ = v.x_; y_ = v.y_;}
// ...
private:
double x_; // The x component of the vector.
double y_; // The y component of the vector.
};
Vector v;
Vector w(v); // calls Vector::Vector(const Vector&)
Vector u = v; // calls Vector::Vector(const Vector&)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 241
Defaulted Copy Constructor
■ defaulted copy constructor performs memberwise copy of its data
members (and bases), where copy performed using: ⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §15.8.1/14]
2 copy constructor for class types
2 bitwise copy for built-in types
■ defaulted copy constructor automatically provided (i.e., implicitly defined)
as public member if none of following user declared: ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §15.8.1/6]
2 move constructor
2 move assignment operator
2 copy assignment operator (if not relying on deprecated behavior)
2 destructor (if not relying on deprecated behavior)
■ example:
// class has defaulted copy constructor
class Widget {
public:
Widget(int i) {i_ = i;}
int get() const {return i_;}
private:
int i_;
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 242
Move Constructor
■ for class T, constructor taking rvalue reference to T as first parameter that
can be called with one argument known as move constructor [C++17 §15.8.1/2]
⁓⁓⁓⁓⁓⁓⁓⁓
■ used to create object by moving from already-existing object
■ move constructor for class T typically is of form T(T&&)
■ example:
class Vector { // Two-dimensional vector class.
public:
Vector() {x_ = 0.0; y_ = 0.0;} // Default constructor
Vector(Vector&& v) {x_ = v.x_; y_ = v.y_;} // Move constructor.
// ...
private:
double x_; // The x component of the vector.
double y_; // The y component of the vector.
};
#include <utility>
Vector v;
Vector w(std::move(v)); // calls Vector::Vector(Vector&&)
Vector x = std::move(w); // calls Vector::Vector(Vector&&)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 243
Defaulted Move Constructor
■ defaulted move constructor performs memberwise move of its data
members (and bases) using: [C++17 §15.8.1/14]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
2 move constructor if available and copy constructor otherwise in case of
class type
2 bitwise copy in case of built-in type
■ defaulted move constructor automatically provided (i.e., implicitly defined)
as public member if none of following user declared: ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §15.8.1/8]
2 copy constructor
2 copy assignment operator
2 move assignment operator
2 destructor
■ example:
// class has defaulted move constructor
struct Widget {
Widget();
void foo();
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 244
Constructor Example
1 class Vector { // Two-dimensional vector class.
2 public:
3 // Default constructor.
4 Vector() {x_ = 0.0; y_ = 0.0;}
5 // Copy constructor.
6 Vector(const Vector& v) {x_ = v.x_; y_ = v.y_;}
7 // Move constructor.
8 Vector(Vector&& v) {x_ = v.x_; y_ = v.y_;}
9 // Another constructor.
10 Vector(double x, double y) {x_ = x; y_ = y;}
11 // ...
12 private:
13 double x_; // The x component of the vector.
14 double y_; // The y component of the vector.
15 };
■ four constructors provided
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 245
Constructor Example (Continued 1)
1 // include definition of Vector class here
2
3 int main() {
4 Vector u;
5 // calls default constructor
6 Vector v(1.0, 2.0);
7 // calls Vector::Vector(double, double)
8 Vector w(v);
9 // calls copy constructor
10 Vector x = u;
11 // calls copy constructor
12 Vector y = Vector(1.0, 0.0);
13 // guaranteed copy/move elision
14 // calls Vector::Vector(double, double), directly
15 // constructing new object in y
16 // does not call move constructor
17 Vector z{Vector()};
18 // guaranteed copy/move elision
19 // calls default constructor, directly constructing
20 // new object in z
21 // does not call move constructor
22 Vector f();
23 // declares function f that returns Vector
24 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 246
Constructor Example (Continued 2)
1 #include <utility>
2 #include <cstdlib>
3 // include definition of Vector class here
4
5 // named RVO not possible
6 Vector func1() {
7 Vector a(1.0, 0.0);
8 Vector b(0.0, 1.0);
9 if (std::rand() % 2) {return a;}
10 else {return b;}
11 }
12
13 // RVO required
14 Vector func2() {return Vector(1.0, 1.0);}
15
16 int main() {
17 Vector u(1.0, 1.0);
18 Vector v(std::move(u));
19 // move constructor invoked to propagate value from u
20 // to v
21 Vector w = func1();
22 // move constructor invoked to propagate value of object
23 // in return statement of func1 to object w in main
24 // (named RVO not possible)
25 Vector x = func2();
26 // move constructor not invoked, due to guaranteed
27 // copy/move elision (return value of func2 directly
28 // constructed in object x in main)
29 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 247
Constructor Initializer Lists
■ in constructor of class, often we want to control which constructor is used
to initialize each data member
■ since all data members are constructed before body of constructor is
entered, this cannot be controlled inside body of constructor
■ to allow control over which constructors are used to initialize individual
data members, mechanism called initializer lists provided
■ initializer list forces specific constructors to be used to initialize individual
data members before body of constructor is entered
■ data members always initialized in order of declaration, regardless of
order in initializer list
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 248
Constructor Initializer List Example
1 class ArrayDouble { // array of doubles class
2 public:
3 ArrayDouble(); // create empty array
4 ArrayDouble(int size); // create array of specified size
5 // ...
6 private:
7 // ...
8 };
9
10 class Vector { // n-dimensional real vector class
11 public:
12 Vector(int size) : data_(size) {}
13 // force data_ to be constructed with
14 // ArrayDouble::ArrayDouble(int)
15 // ...
16 private:
17 ArrayDouble data_; // elements of vector
18 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 249
Default Member Initializers
■ can provide default values with which to initialize data members
■ if initializer for data member not given in constructor initalizer list, default
member initializer used if specified
■ example:
1 #include <string>
2
3 struct Widget {
4 Widget() {}
5 // constructor behaves as if it had initializer
6 // list:
7 // answer(42), message("hello")
8 int answer = 42;
9 std::string message = "hello";
10 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 250
Member Initialization Order
■ recall that data members initialized in order of declaration in class
definition
■ failing to consider this fact can easily leads to bugs in code
■ for example, consider following code:
1 #include <cassert>
2
3 class Widget {
4 public:
5 Widget() : y_(42), x_(y_ + 1) {assert(x_ == 43);}
6 int x_;
7 int y_;
8 };
9
10 int main() {
11 Widget w;
12 }
■ in Widget’s default constructor, x_ initialized before y_, which results in
use of y_ before its initialization
■ therefore, above code has undefined behavior
■ in practice, likely x_ will simply have garbage value when body of
constructor executes and assertion will fail
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 251
Destructors
■ when object reaches end of lifetime, typically some cleanup required
before object passes out of existence
■ destructor is member function that is automatically called when object
reaches end of lifetime in order to perform any necessary cleanup
■ often object may have allocated resources associated with it (e.g.,
memory, files, devices, network connections, processes/threads)
■ when object destroyed, must ensure that any resources associated with
object are released
■ destructors often serve to release resources associated with object
■ destructor for class T always has name T::~T
■ destructor has no return type (not even void)
■ destructor cannot be overloaded
■ destructor always takes no parameters
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 252
Defaulted Destructor
■ defaulted destructor performs no clean-up action, except to destroy
each of its data members (and bases)
■ defaulted destructor automatically provided (i.e., implicitly defined) if no
user-declared destructor [C++17 §15.4/4]
⁓⁓⁓⁓⁓⁓⁓⁓
■ for classes that require additional clean-up, defaulted destructor will not
yield correct behavior
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 253
Destructor Example
■ example:
class Widget {
public:
Widget(int bufferSize) { // Constructor.
// allocate some memory for buffer
bufferPtr_ = new char[bufferSize];
}
~Widget() { // Destructor.
// free memory previously allocated
delete [] bufferPtr_;
}
// copy constructor, assignment operator, ...
private:
char* bufferPtr_; // pointer to start of buffer
};
■ if defaulted destructor were used, memory associated with bufferPtr_
would not be freed
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 254
Section 2.4.3
Operator Overloading
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 255
Operator Overloading
■ can specify meaning of operator whose operands are one or more
user-defined types through process known as operator overloading
■ operators that can be overloaded:
arithmetic + - * / %
bitwise ^ & | ~ << >>
logical ! && ||
relational == != <=> < > <= >=
assignment =
compound assignment += -= *= /= %= ^= &= |= <<= >>=
increment/decrement ++ --
subscript []
function call ()
address, indirection & *
others ->* , -> new delete
■ not possible to change precedence/associativity or syntax of operators
■ meaning of operator specified by specially named function
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 256
Operator Overloading (Continued 1)
■ operator @ overloaded via special function named operator@
■ with some exceptions, operator can be overloaded as member function or
nonmember function
■ if operator overloaded as member function, first operand provided as
*this and remaining operands, if any, provided as function parameters
■ if operator overloaded as nonmember function, all operands provided as
function parameters
■ postfix unary (increment/decrement) operators take additional dummy
parameter of type int in order to distinguish from prefix case
■ expressions involving overloaded operators interpreted as follows:
Interpretation As
Type Expression Member Function Nonmember Function
Binary a@b a.operator@(b) operator@(a, b)
Prefix unary @a a.operator@() operator@(a)
Postfix unary a@ a.operator@(i) operator@(a, i)
i is dummy parameter of type int
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 257
Operator Overloading (Continued 2)
■ assignment, function-call, subscript, and member-selection operators
must be overloaded as member functions [C++17 §16.5.3/1] [C++17 §16.5.4/1] [C++17 §16.5.5/1]
⁓⁓⁓⁓⁓⁓⁓⁓ ⁓⁓⁓⁓⁓⁓⁓⁓ ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §16.5.6/1]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ if member and nonmember functions both defined, argument matching
rules determine which is called
■ if first operand of overloaded operator not object of class type, must use
nonmember function
■ for most part, operators can be defined quite arbitrarily for user-defined
types
■ for example, no requirement that “++x”, “x += 1”, and “x = x + 1” be
equivalent
■ of course, probably not advisable to define operators in very
counterintuitive ways, as will inevitably lead to bugs in code
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 258
Operator Overloading (Continued 3)
■ some examples showing how expressions translated into function calls
are as follows:
Expression Member Function Nonmember Function
y = x y.operator=(x) —
y += x y.operator+=(x) operator+=(y, x)
x + y x.operator+(y) operator+(x, y)
++x x.operator++() operator++(x)
x++ x.operator++(int) operator++(x, int)
x == y x.operator==(y) operator==(x, y)
x < y x.operator<(y) operator<(x, y)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 259
Operator Overloading Example: Vector
1 class Vector { // Two-dimensional vector class
2 public:
3 Vector() : x_(0.0), y_(0.0) {}
4 Vector(double x, double y) : x_(x), y_(y) {}
5 double x() const { return x_; }
6 double y() const { return y_; }
7 private:
8 double x_; // The x component
9 double y_; // The y component
10 };
11
12 // Vector addition
13 Vector operator+(const Vector& u, const Vector& v)
14 {return Vector(u.x() + v.x(), u.y() + v.y());}
15
16 // Dot product
17 double operator*(const Vector& u, const Vector& v)
18 {return u.x() * v.x() + u.y() * v.y();}
19
20 void func() {
21 Vector u(1.0, 2.0);
22 Vector v(u);
23 Vector w;
24 w = u + v; // w.operator=(operator+(u, v))
25 double c = u * v; // calls operator*(u, v)
26 // since c is built-in type, assignment operator
27 // does not require function call
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 260
Operator Overloading Example: Array10
1 class Array10 { // Ten-element real array class
2 public:
3 Array10() {
4 for (int i = 0; i < 10; ++i) { // Zero array
5 data_[i] = 0;
6 }
7 }
8 const double& operator[](int index) const {
9 return data_[index];
10 }
11 double& operator[](int index) {
12 return data_[index];
13 }
14 private:
15 double data_[10]; // array data
16 };
17
18 void func() {
19 Array10 v;
20 v[1] = 3.5; // calls Array10::operator[](int)
21 double c = v[1]; // calls Array10::operator[](int)
22 const Array10 u;
23 u[1] = 2.5; // ERROR: u[1] is const
24 double d = u[1]; // calls Array10::operator[](int) const
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 261
Operator Overloading: Member vs. Nonmember Functions
■ in most cases, operator can be overloaded as either member or
nonmember function
■ some considerations that factor into decision of whether to use member or
nonmember function given below
■ if access to private members is required, using member function may be
preferable to having nonmember friend function
■ if first operand of operator is of non-class type, must use nonmember
function; otherwise, either member or nonmember could be used
■ if conversions for first argument to operator are desired, must use
nonmember function; if such conversions not desired, must use member
function
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 262
Overloading as Member vs. Nonmember: Example
1 class Complex { // Complex number type.
2 public:
3 Complex(double x, double y) : x_(x), y_(y) {}
4 double real() const {return x_;}
5 double imag() const {return y_;}
6 // Alternatively, overload operator+ as a member function.
7 // Complex operator+(double b) const
8 // {return Complex(real() + b, imag());}
9 private:
10 double x_; // The real part.
11 double y_; // The imaginary part.
12 };
13
14 // Overload as a nonmember function.
15 // (A member function could instead be used. See above.)
16 Complex operator+(const Complex& a, double b)
17 {return Complex(a.real() + b, a.imag());}
18
19 // This can only be accomplished with a nonmember function.
20 Complex operator+(double b, const Complex& a)
21 {return Complex(b + a.real(), a.imag());}
22
23 void myFunc() {
24 Complex a(1.0, 2.0);
25 Complex b(1.0, -2.0);
26 double r = 2.0;
27 Complex c = a + r; /* could use nonmember or member function
28 operator+(a, r) or a.operator+(r) */
29 Complex d = r + a; /* must use nonmember function
30 operator+(r, a), since r.operator+(a) will not work */
31 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 263
Overloading as Member vs. Nonmember: Example
1 #include <string_view>
2
3 class Widget {
4 public:
5 Widget();
6 Widget(std::string_view); // converting constructor
7 operator std::string_view() const; // conversion operator
8 // ...
9 };
10
11 // overload as nonmember function
12 Widget operator+(Widget, std::string_view);
13
14 int main() {
15 Widget w;
16 std::string_view sv("hello");
17 Widget a = w + sv;
18 /* OK: operator+(Widget, std::string_view) called
19 with no conversions necessary */
20 Widget b = sv + w;
21 /* OK: operator+(Widget, std::string_view) called, where
22 first argument implicitly converted to Widget by
23 Widget’s converting constructor and second argument
24 implicitly converted to std::string_view by
25 Widget’s conversion operator; if operator+ were
26 overloaded as member of Widget class, compiler error
27 would result as overload resolution would fail to
28 yield any viable function to call */
29 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 264
Copy Assignment Operator
■ for class T, T::operator= having exactly one parameter that is lvalue
reference to T known as copy assignment operator ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §15.8.2/1]
■ used to assign, to already-existing object, value of another object by
copying
■ copy assignment operator for class T typically is of form
T& operator=(const T&) (returning reference to *this)
■ copy assignment operator returns (nonconstant) reference in order to
allow for statements like following to be valid (where x, y, and z are of
type T and T::modify is a non-const member function):
x = y = z; // x.operator=(y.operator=(z))
(x = y) = z; // (x.operator=(y)).operator=(z)
(x = y).modify(); // (x.operator=(y)).modify()
■ must be careful to correctly consider case of self-assignment
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 265
Defaulted Copy Assignment Operator
■ defaulted copy assignment operator performs memberwise copy of its
data members (and bases), where copy performed using: [C++17 §15.8.2/12]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
2 copy assignment operator for class types
2 bitwise copy for built-in types
■ defaulted copy assignment operator automatically provided (i.e., implicitly
defined) as public member if none of following user declared: [C++17 §15.8.2/2]
⁓⁓⁓⁓⁓⁓⁓⁓
2 move constructor
2 move assignment operator
2 copy constructor (if not relying on deprecated behavior)
2 destructor (if not relying on deprecated behavior)
■ example:
// class has implicitly-defined defaulted
// copy-assignment operator
class Widget {
public:
Widget(int i) {i_ = i;}
int get() const {return i_;}
private:
int i_;
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 266
Self-Assignment Example
■ in practice, self assignment typically occurs when references (or pointers)
are involved
■ example:
void doSomething(SomeType& x, SomeType& y) {
x = y; // self assignment if &x == &y
// ...
}
void myFunc() {
SomeType z;
// ...
doSomething(z, z); // results in self assignment
// ...
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 267
Move Assignment Operator
■ for class T, T::operator= having exactly one parameter that is rvalue
reference to T known as move assignment operator [C++17 §15.8.2/3]
⁓⁓⁓⁓⁓⁓⁓⁓
■ used to assign, to already-existing object, value of another object by
moving
■ move assignment operator for class T typically is of form
T& operator=(T&&) (returning reference to *this)
■ move assignment operator returns (nonconstant) reference for same
reason as in case of copy assignment operator
■ in case of move, self-assignment should probably not occur, but might be
prudent to test for this with assertion in order to protect against “insane”
code
■ standard library effectively forbids self-assignment for move ⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §20.5.4.9/(1.3)]
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 268
Defaulted Move Assignment Operator
■ defaulted move assignment operator performs memberwise move of its
data members (and bases) where move performed using: [C++17 §15.8.2/12]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
2 if class type: move assignment operator if available and copy assignment
operator otherwise
2 if built-in type: bitwise copy
■ defaulted move assignment operator automatically provided (i.e., implicitly
defined) as public member if none of following user declared: [C++17 §15.8.2/4]
⁓⁓⁓⁓⁓⁓⁓⁓
2 copy constructor
2 move constructor
2 copy assignment operator
2 destructor
■ example:
#include <vector>
// class has implicitly-defined defaulted
// move-assignment operator
struct Widget {
Widget();
std::vector<int> v;
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 269
Copy/Move Assignment Operator Example: Complex
1 class Complex {
2 public:
3 Complex(double x = 0.0, double y = 0.0) :
4 x_(x), y_(y) {}
5 Complex(const Complex& a) : x_(a.x_), y_(a.y_) {}
6 Complex(Complex&& a) : x_(a.x_), y_(a.y_) {}
7 Complex& operator=(const Complex& a) { // Copy assign
8 if (this != &a) {
9 x_ = a.x_; y_ = a.y_;
10 }
11 return *this;
12 }
13 Complex& operator=(Complex&& a) { // Move assign
14 x_ = a.x_; y_ = a.y_;
15 return *this;
16 }
17 private:
18 double x_; // The real part.
19 double y_; // The imaginary part.
20 };
21
22 int main() {
23 Complex z(1.0, 2.0);
24 Complex v(1.5, 2.5);
25 v = z; // v.operator=(z)
26 v = Complex(0.0, 1.0); // v.operator=(Complex(0.0, 1.0))
27 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 270
Assignment Operator Example: Buffer
1 class Buffer { // Character buffer class.
2 public:
3 Buffer(int bufferSize) { // Constructor.
4 bufSize_ = bufferSize;
5 bufPtr_ = new char[bufferSize];
6 }
7 Buffer(const Buffer& buffer) { // Copy constructor.
8 bufSize_ = buffer.bufSize_;
9 bufPtr_ = new char[bufSize_];
10 for (int i = 0; i < bufSize_; ++i)
11 bufPtr_[i] = buffer.bufPtr_[i];
12 }
13 ~Buffer() { // Destructor.
14 delete [] bufPtr_;
15 }
16 Buffer& operator=(const Buffer& buffer) { // Copy assignment operator.
17 if (this != &buffer) {
18 delete [] bufPtr_;
19 bufSize_ = buffer.bufSize_;
20 bufPtr_ = new char[bufSize_];
21 for (int i = 0; i < bufSize_; ++i)
22 bufPtr_[i] = buffer.bufPtr_[i];
23 }
24 return *this;
25 }
26 // ...
27 private:
28 int bufSize_; // buffer size
29 char* bufPtr_; // pointer to start of buffer
30 };
■ without explicitly-provided assignment operator (i.e., with defaulted
assignment operator), memory leaks and memory corruption would result
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 271
Section 2.4.4
Miscellany
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 272
std::initializer_list Class Template
■ class template std::initializer_list provides lightweight list type
■ in order to use initializer_list, need to include header file
initializer_list
■ declaration:
template <class T> initializer_list;
■ T is type of elements in list
■ initializer_list is very lightweight
■ can query number of elements in list and obtain iterators to access these
elements
■ initializer_list often useful as parameter type for constructor
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 273
std::initializer_list Example
1 #include <iostream>
2 #include <vector>
3
4 class Sequence {
5 public:
6 Sequence(std::initializer_list<int> list) {
7 for (std::initializer_list<int>::const_iterator i =
8 list.begin(); i != list.end(); ++i)
9 elements_.push_back(*i);
10 }
11 void print() const {
12 for (std::vector<int>::const_iterator i =
13 elements_.begin(); i != elements_.end(); ++i)
14 std::cout << *i << ’\n’;
15 }
16 private:
17 std::vector<int> elements_;
18 };
19
20 int main() {
21 Sequence seq = {1, 2, 3, 4, 5, 6};
22 seq.print();
23 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 274
Converting Constructors
■ constructor that is not declared with explicit specifier is called
converting constructor
■ converting constructor can be used for implicit conversions
■ example:
1 #include <string>
2 using namespace std::literals;
3
4 class Widget {
5 public:
6 Widget(const std::string&); // converting constructor
7 Widget(const char*, int); // converting constructor
8 // ...
9 };
10
11 int main() {
12 Widget v = "hello"s;
13 // invokes Widget::Widget(const std::string&)
14 Widget w = {"goodbye", 4};
15 // invokes Widget::Widget(const char*, int)
16 v = "bonjour"s; // invokes Widget::Widget(const std::string&)
17 w = {"au revoir", 2};
18 // invokes Widget::Widget(const char*, int)
19 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 275
Explicit Constructors
■ converting constructor can be used for implicit conversions (e.g., when
attempting to obtain matching type for function parameter in function call)
and copy initialization
■ often, desirable to prevent constructor from being used in these contexts
■ to accommodate this, constructor can be marked as explicit
■ explicit constructor is constructor that cannot be used for performing
implicit conversions or copy initialization
....................
■ prefixing constructor declaration with explicit keyword makes
constructor explicit
■ to conditionally make constructor explicit, explicit keyword can be
followed by parenthesized constant expression of type bool, in which
case constructor is explicit if expression evaluates to true
■ example:
class Widget {
public:
explicit Widget(int); // explicit constructor
// ...
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 276
Example Without Explicit Constructor
1 #include <cstdlib>
2
3 // one-dimensional integer array class
4 class IntArray {
5 public:
6 // create array of int with size elements
7 IntArray(std::size_t size) { /* ... */ };
8 // ...
9 };
10
11 void processArray(const IntArray& x) {
12 // ...
13 }
14
15 int main() {
16 // following lines of code almost certain to be
17 // incorrect, but valid due to implicit type
18 // conversion provided by
19 // IntArray::IntArray(std::size_t)
20 IntArray a = 42;
21 // probably incorrect
22 // implicit conversion effectively yields code:
23 // IntArray a = IntArray(42);
24 processArray(42);
25 // probably incorrect
26 // implicit conversion effectively yields code:
27 // processArray(IntArray(42));
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 277
Example With Explicit Constructor
1 #include <cstdlib>
2
3 // one-dimensional integer array class
4 class IntArray {
5 public:
6 // create array of int with size elements
7 explicit IntArray(std::size_t size) { /* ... */ };
8 // ...
9 };
10
11 void processArray(const IntArray& x) {
12 // ...
13 }
14
15 int main() {
16 IntArray a = 42; // ERROR: cannot convert
17 processArray(42); // ERROR: cannot convert
18 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 278
Conditionally Explicit Constructor Example
1 #include <type_traits>
2 #include <utility>
3 #include <concepts>
4
5 struct im {
6 im(int) {} // not explicit
7 };
8
9 struct ex {
10 explicit ex(int) {} // explicit
11 };
12
13 template<class T>
14 class wrapper {
15 public:
16 template<class U> requires std::constructible_from<T, U>
17 explicit(!std::is_convertible_v<U, T>) // conditionally explicit
18 wrapper(U&& u) : t_(std::forward<U>(u)) {}
19 // ...
20 private:
21 T t_;
22 };
23
24 int main() {
25 wrapper<im> i = 42; // OK: wrapper constructor not explicit
26 // wrapper<ex> e = 42; // ERROR: wrapper constructor explicit
27 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 279
Conversion Operators
■ conversion operator enables conversion from class type to another type
■ conversion operator to convert from class T to another type must be
provided as (nonstatic) member function of T
■ member function takes no parameters (except implicit this parameter)
and has no explicit return type
■ requiring conversion operator to be member function likely motivated by
desire to allow code for performing conversions to be more easily
identified (i.e., only need to examine conversion operators and converting
constructors for at most two classes)
■ example:
class Widget {
public:
operator bool(); // conversion operator
// ...
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 280
Explicit Conversion Operators
■ by default, conversion operator can be used for implicit conversions
■ sometimes, desirable to prevent conversion operator from being used in
such contexts
■ to accommodate this, conversion operator can be marked as explicit
■ prefixing conversion operator declaration with explicit keyword makes
conversion operator explicit
■ to conditionally make conversion operator explicit, explicit keyword
can be followed by parenthesized constant expression of type bool, in
which case conversion operator is explicit if expression evaluates to true
■ example:
class Widget {
public:
explicit operator int(); // explicit conversion operator
// note: "explicit" is equivalent to "explicit(true)"
// ...
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 281
Conversion Operator Example
1 #include <iostream>
2 #include <string>
3 #include <cassert>
4 using namespace std::literals;
5
6 class Widget {
7 public:
8 explicit operator int() const {return 42;}
9 operator std::string() const {return "Widget"s;}
10 // ...
11 };
12
13 int main() {
14 Widget w;
15 int i(w);
16 // direct initialization can use explicit conversion operator;
17 // uses conversion operator to convert Widget to int
18 assert(i == 42);
19 // int j = w;
20 // ERROR: copy initialization requires implicit conversion and
21 // conversion operator that converts Widget to int is explicit
22 int j = static_cast<int>(w);
23 // uses (explicit) conversion operator to convert Widget to int
24 std::string s(w);
25 // uses conversion operator to convert Widget to std::string
26 assert(s == "Widget"s);
27 std::string t = w;
28 // uses conversion operator to convert Widget to std::string
29 assert(t == "Widget"s);
30 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 282
Explicitly Deleted/Defaulted Special Member Functions
■ can explicitly default or delete special member functions (i.e., default
constructor, copy constructor, move constructor, destructor, copy
assignment operator, and move assignment operator)
■ can also delete non-special member functions
■ example:
class Thing {
public:
Thing() = default;
// Prevent copying.
Thing(const Thing&) = delete;
Thing& operator=(const Thing&) = delete;
Thing(Thing&&) = default;
Thing& operator=(Thing&&) = default;
~Thing() = default;
// ...
};
// Thing is movable but not copyable.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 283
Delegating Constructors
■ sometimes, one constructor of class needs to performs all work of another
constructor followed by some additional work
■ rather than duplicate common code in both constructors, one constructor
can use its initializer list to invoke other constructor (which must be only
one in initializer list)
■ constructor that invokes another constructor via initializer list called
delegating constructor
■ example:
1 class Widget {
2 public:
3 Widget(char c, int i) : c_(c), i_(i) {}
4 Widget(int i) : Widget(’a’, i) {}
5 // delegating constructor
6 // ...
7 private:
8 char c_;
9 int i_;
10 };
11
12 int main() {
13 Widget w(’A’, 42);
14 Widget v(42);
15 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 284
Static Data Members
■ sometimes want to have object that is shared by all objects of class
■ data member that is shared by all objects of class is called static data
member
■ to make data member static, declare using static qualifier
■ static data member must (in most cases) be defined outside body of class
■ example:
1 class Widget {
2 public:
3 Widget() {++count_;}
4 Widget(const Widget&) {++count_;}
5 Widget(Widget&&) {++count_;}
6 ~Widget() {--count_;}
7 // ...
8 private:
9 static int count_;
10 // total number of Widget objects in existence
11 };
12
13 // Define (and initialize) count member.
14 int Widget::count_ = 0;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 285
Static Member Functions
■ sometimes want to have member function that does not operate on
objects of class
■ member function of class that does not operate on object of class (i.e.,
has no this variable) called static member function
■ to make member function static, declare using static qualifier
■ example:
1 class Widget {
2 public:
3 // ...
4 // convert degrees to radians
5 static double degToRad(double deg)
6 {return (M_PI / 180.0) * deg;}
7 private:
8 // ...
9 };
10
11 void func() {
12 Widget x; double rad;
13 rad = Widget::degToRad(45.0);
14 rad = x.degToRad(45.0); // x is ignored
15 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 286
constexpr Member Functions
■ like non-member functions, member functions can also be qualified as
constexpr to indicate function can be computed at compile time
provided that all arguments to function are constant expressions
■ some additional restrictions on constexpr member functions relative to
nonmember case
■ constexpr member function implicitly inline
■ constexpr member function not implicitly const (as of C++14)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 287
constexpr Constructors and Destructors
■ constructors and destructors can also be qualified as constexpr
■ constexpr constructors and constexpr destructors must meet all
requirements of constexpr functions
■ some restrictions on what types can have constexpr constructors and
constexpr destructors and what such constructors and destructors can do
(e.g., no virtual base classes)
■ these restrictions discussed in some detail later
■ constexpr constructors and constexpr destructors are implicitly inline
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 288
Example: Constexpr Constructors and Member Functions
1 #include <iostream>
2
3 // Two-dimensional vector class.
4 class Vector {
5 public:
6 constexpr Vector() : x_(0), y_(0) {}
7 constexpr Vector(double x, double y) : x_(x), y_(y) {}
8 constexpr Vector(const Vector& v) : x_(v.x_), y_(v.y_) {}
9 constexpr Vector& operator=(const Vector& v)
10 {x_ = v.x_; y_ = v.y_; return *this;}
11 constexpr double x() const {return x_;}
12 constexpr double y() const {return y_;}
13 constexpr double squared_norm() const
14 {return x_ * x_ + y_ * y_;}
15 // ...
16 private:
17 double x_; // The x component of the vector.
18 double y_; // The y component of the vector.
19 };
20
21 int main() {
22 constexpr Vector v(3.0, 4.0);
23 static_assert(v.x() == 3.0 && v.y() == 4.0);
24 constexpr double d = v.squared_norm();
25 std::cout << d << ’\n’;
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 289
Why Constexpr Member Functions Are Not Implicitly Const
1 class Widget {
2 public:
3 constexpr Widget() : i_(42) {}
4 constexpr const int& get() const {return i_;}
5 constexpr int& get() /* what if implicitly const? */
6 {return i_;}
7 // ...
8 private:
9 int i_;
10 };
11
12 constexpr Widget w;
13 static_assert(w.get() == 42);
14 // invokes const member function
15 constexpr int i = ++Widget().get();
16 // invokes non-const member function
17 static_assert(i == 43);
■ in above code example, we want to have const and non-const overloads of get
member function that can each be used in constant expressions
■ so both overloads of get need to be constexpr
■ if constexpr member functions were implicitly const, it would be impossible to
overload on const in manner we wish to do here, since second overload of get
would automatically become const member function (resulting in multiple
conflicting definitions of const member function get)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 290
The mutable Qualifier
■ type for nonstatic data member can be qualified as mutable meaning
that member does not affect externally visible state of class object
■ mutable data member can be modified in const member function
■ mutable qualifier often used for mutexes, condition variables, cached
values, statistical information for performance analysis or debugging
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 291
Example: Mutable Qualifier for Statistical Information
1 #include <iostream>
2 #include <string>
3
4 class Employee {
5 public:
6 Employee(int id, std::string& name, double salary) :
7 id_(id), name_(name), salary_(salary), accessCount_(0) {}
8 int getId() const {
9 ++accessCount_; return id_;
10 }
11 std::string getName() const {
12 ++accessCount_; return name_;
13 }
14 double getSalary() const {
15 ++accessCount_; return salary_;
16 }
17 // ...
18 // for debugging
19 void outputDebugInfo(std::ostream& out) const {
20 out << accessCount_ << ’\n’;
21 }
22 private:
23 int id_; // employee ID
24 std::string name_; // employee name
25 double salary_; // employee salary
26 mutable unsigned long accessCount_; // for debugging
27 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 292
Pointers to Members
■ pointer to member provides means to reference particular nonstatic
member of class, independent of any class object instance
■ pointer to member can only be formed for nonstatic (data or function)
member of class
■ can obtain pointer to member that references nonstatic member m in class
T by applying address-of operator to T::m (i.e., using expression &T::m)
■ special value nullptr can be given to pointer to member to indicate
that pointer to member does not refer to any member
■ pointer to member of class T written as T::*
■ type of pointer to member embodies type of class and type of member
within class
■ example:
2 int Widget::* is pointer to member of Widget class having type int
2 const int Widget::* is pointer to member of Widget class having
type const int
2 float (Gadget::*)(int) const is pointer to const member function
of Gadget class that takes single int parameter and has return type of
float
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 293
Pointers to Members (Continued)
■ since pointer to member is not associated with any class object instance,
dereferencing pointer to member requires object (or pointer to object) to
be specified
■ given object x of type T, can access member through pointer to member
ptm by applying member-selection operator .* to x using expression
x.*ptm
■ given pointer p to object of type T, can access member through pointer to
member ptm by applying member-selection operator ->* to p using
expression p->*ptm
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 294
Pointers to Members for Data Members
■ conceptually, pointer to member for (nonstatic) data member can be
thought of as offset (in memory) from start of class object to start of data
member (i.e., location of data member relative to start of class object)
■ since pointer to member does not identify particular object instance (i.e.,
value for this), pointer to member alone not sufficient to specify
particular instance of member in object
■ consequently, when dereferencing pointer to member, must always
specify object (or pointer to object)
■ example:
1 struct Widget {
2 int i;
3 inline static int j;
4 };
5
6 int main(){
7 Widget w, v;
8 int Widget::* ptm = &Widget::i; // pointer to member
9 int* jp = &Widget::j;
10 // address of static member is ordinary pointer
11 w.*ptm = 42; // w.*ptm references w.i
12 v.*ptm = 42; // v.*ptm references v.i
13 *jp = 42; // references Widget::j
14 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 295
Pointers to Members and Const Example
1 #include <type_traits>
2
3 template <class T1, class T2, class T3>
4 struct triplet {
5 triplet(T1 first_, T2 second_, T3 third_) :
6 first(first_), second(second_), third(third_) {}
7 T1 first;
8 T2 second;
9 T3 third;
10 };
11
12 int main() {
13 using widget = triplet<const int, int, double>;
14 widget w(1, 1, 1.0);
15 static_assert(std::is_same_v<decltype(&widget::first),
16 const int widget::*>);
17 static_assert(std::is_same_v<decltype(&widget::second),
18 int widget::*>);
19 static_assert(std::is_same_v<decltype(&widget::third),
20 double widget::*>);
21 const int widget::* cp = nullptr;
22 int widget::* p = nullptr;
23 cp = &widget::first; // OK: constness of pointee same
24 cp = &widget::second; // OK: adds const to pointee
25 // p = &widget::first; // ERROR: discards const from pointee
26 p = &widget::second; // OK: constness of pointee same
27 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 296
Pointers to Members for Function Members
■ pointer to member for (nonstatic) member function simply identifies
particular member function of class (independent of any object instance)
■ since pointer to member does not identify particular object instance (i.e.,
value for this), pointer to member alone not sufficient to invoke member
function
■ consequently, when dereferencing pointer to member, must always
specify object (or pointer to object) so that this parameter can be set
appropriately
■ example:
1 struct Widget {
2 void func() {/* ... */}
3 static void set_verbosity(int level) {/* ... */}
4 };
5
6 int main() {
7 Widget w, v;
8 void (Widget::* ptm)() = &Widget::func; // pointer to member
9 void (*pf)(int) = &Widget::set_verbosity;
10 // address of static member is ordinary pointer
11 (w.*ptm)(); // calls w.func()
12 (v.*ptm)(); // calls v.func()
13 (*pf)(42); // calls Widget::set_verbosity()
14 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 297
Pointers to Members: Example
1 #include <string>
2 #include <cassert>
3
4 struct Widget {
5 std::string s;
6 int i = 0;
7 void clear() {i = 0; s = "";}
8 };
9
10 int main() {
11 Widget w;
12 Widget* wp = &w;
13
14 // pointer to member of Widget of type int
15 int Widget::* iptm = nullptr;
16 // w.*iptm = 42; // ERROR: null pointer to member
17 iptm = &Widget::i; // iptm references i member of Widget
18 w.*iptm = 42; // w.*iptm references w.i
19 assert(w.i == 42);
20
21 // pointer to member of Widget of type std::string
22 std::string Widget::* sptm = &Widget::s;
23 wp->*sptm = "hello"; // wp->*sptm references w.s
24 assert(w.s == "hello");
25
26 // pointer to member of Widget that is function that takes
27 // no parameters and returns void
28 void (Widget::* fptm)() = &Widget::clear;
29 (w.*fptm)(); // w.*fptm references w.clear
30 assert(w.i == 0 && w.s == "");
31 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 298
Pointers to Members: Example
1 #include <iostream>
2
3 class Widget {
4 public:
5 Widget(bool flag) {
6 op_ = flag ? &Widget::op_2 : &Widget::op_1;
7 }
8 void modify() {
9 // ...
10 (this->*op_)(); // invoke member function
11 // ...
12 }
13 // ...
14 private:
15 void op_1() {std::cout << "op_1 called\n";}
16 void op_2() {std::cout << "op_2 called\n";}
17 void (Widget::*op_)();
18 // pointer to member function of Widget class that
19 // takes no parameters and returns no value
20 // ...
21 };
22
23 int main() {
24 Widget u(false);
25 Widget v(true);
26 u.modify(); // modify invokes op_1
27 v.modify(); // modify invokes op_2
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 299
Pointers to Members Example: Accumulate
1 #include <iostream>
2 #include <iterator>
3
4 struct Point {double x; double y;};
5 struct Thing {int i; float f;};
6
7 template <auto P, class Iter, class T>
8 T accumulate(Iter first, Iter last, T init_sum) {
9 for (auto i = first; i != last; ++i)
10 {init_sum += i->*P;}
11 return init_sum;
12 }
13
14 int main() {
15 constexpr Point p[]{{1.0, 21.0}, {0.5, 21.0}, {0.5, 0.0}};
16 constexpr Thing t[]{{1, 0.1f}, {2, 0.1f}, {3, 0.1f}};
17 std::cout
18 << accumulate<&Point::x>(std::begin(p), std::end(p), 0.0) << ’ ’
19 << accumulate<&Point::y>(std::begin(p), std::end(p), 0.0) << ’\n’;
20 std::cout
21 << accumulate<&Thing::i>(std::begin(t), std::end(t), 0) << ’ ’
22 << accumulate<&Thing::f>(std::begin(t), std::end(t), 0.0f) << ’\n’;
23 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 300
Pointers to Members Example: Statistics Calculation
1 #include <iostream>
2
3 template <auto Count, auto Sum, class T, class Value>
4 void update_statistics(T& stats, Value value) {
5 ++(stats.*Count); // adjust count of values
6 stats.*Sum += value; // adjust sum of values
7 }
8
9 struct Widget {
10 int count = 0; // count
11 double sum = 0; // sum
12 short int si;
13 };
14
15 struct Gadget {
16 int n = 0; // count
17 double d;
18 double sigma = 0; // sum
19 };
20
21 int main() {
22 Widget w;
23 Gadget g;
24 for (auto&& x : {0.5, 1.5, 2.5}) {
25 update_statistics<&Widget::count, &Widget::sum>(w, x);
26 update_statistics<&Gadget::n, &Gadget::sigma>(g, x);
27 }
28 std::cout << w.sum / static_cast<double>(w.count) << ’\n’;
29 std::cout << g.sigma / static_cast<double>(g.n) << ’\n’;
30 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 301
Stream Inserters
■ stream inserters write data to output stream
■ overload operator<<
■ have general form
std::ostream& operator<<(std::ostream&, T) where type T is
typically const lvalue reference type
■ example:
std::ostream& operator<<(std::ostream& outStream,
const Complex& a)
{
outStream << a.real() << ’ ’ << a.imag();
return outStream;
}
■ inserter and extractor should use compatible formats (i.e., what is written
by inserter should be readable by extractor)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 302
Stream Extractors
■ stream extractors read data from input stream
■ overload operator>>
■ have general form
std::istream& operator>>(std::istream&, T) where type T is
typically non-const lvalue reference type
■ example:
std::istream& operator>>(std::istream& inStream,
Complex& a)
{
double real = 0.0;
double imag = 0.0;
inStream >> real >> imag;
a = Complex(real, imag);
return inStream;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 303
Structured Bindings
■ structured bindings allow, with single statement, multiple variables to be
declared and initialized with values from pair, tuple, array, or struct
■ declaration uses auto keyword
■ variables enclosed in brackets
■ multiple variables separated by commas
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 304
Structured Bindings Example
1 #include <tuple>
2 #include <array>
3 #include <cassert>
4
5 int main() {
6 int a[3] = {1, 2, 3};
7 auto [a0, a1, a2] = a;
8 assert(a0 == a[0] && a1 == a[1] && a2 == a[2]);
9
10 int b[3] = {0, 2, 3};
11 auto& [b0, b1, b2] = b;
12 ++b0;
13 assert(b[0] == 1);
14
15 std::array<int, 3> c = {1, 2, 3};
16 auto [c0, c1, c2] = c;
17 assert(c0 == c[0] && c1 == c[1] && c2 == c[2]);
18
19 auto t = std::tuple(true, 42, ’A’);
20 auto [tb, ti, tc] = t;
21 assert(tb == true && ti == 42 && tc == ’A’);
22 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 305
Structured Bindings Example
1 #include <map>
2 #include <string>
3 #include <iostream>
4
5 int main() {
6 std::map<std::string, int> m = {
7 {"apple", 1},
8 {"banana", 2},
9 {"orange", 3},
10 };
11 for (auto&& [key, value] : m) {
12 std::cout << key << ’ ’ << value << ’\n’;
13 }
14 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 306
Literal Types
■ each of following types said to be literal type: [C++20 §6.8/10]
⁓⁓⁓⁓⁓⁓⁓⁓
2 void
2 scalar type (e.g., integral, floating point, pointer, enumeration, pointer to
member)
2 reference type
2 array of literal type
2 class type that has all of following properties:
2 has constexpr destructor
2 is either closure type or aggregate type or has at least one constexpr
constructor or constructor template (possibly inherited) that is not copy or
move constructor
2 if is union, at least one nonstatic data member is of nonvolatile literal type
2 if is not union, all nonstatic data members and base classes are of nonvolatile
literal types
■ examples of literal types:
2 int, double[16], std::vector<int>, and std::string
■ examples of types that are not literal types:
2 std::list and std::set
■ literal types important in context of constexpr variables, functions, and
constructors
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 307
Example: Literal Types
1 // literal type
2 class Widget {
3 public:
4 constexpr Widget(int i = 0) : i_(i) {}
5 ~Widget() = default; // constexpr destructor
6 private:
7 int i_;
8 };
9
10 // not literal type
11 class Gadget {
12 public:
13 constexpr Gadget() {}
14 ~Gadget() {} // non-constexpr destructor
15 };
16
17 // not literal type
18 // no constexpr constructor, excluding copy/move constructor
19 class Foo {
20 public:
21 Foo() {};
22 ~Foo() = default; // constexpr destructor
23 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 308
Constexpr Variable Requirements
■ constexpr variable must satisfy following requirements: ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++20 §9.2.5/10]
2 its type must be literal type
2 it must be immediately initialized
2 full expression of its initialization must be constant expression (including all
implicit conversions and constructor calls)
2 it must have constant destruction [C++20 §7.7/7]
⁓⁓⁓⁓⁓⁓⁓
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 309
Example: Constexpr Variable Requirement Violations
1 #include <set>
2 #include <vector>
3
4 constexpr std::set s{1, 2, 3};
5 // ERROR: not literal type
6
7 constexpr int i;
8 // ERROR: not initialized
9
10 float func();
11 constexpr float f = func();
12 // ERROR: initializer is not constant expression since func is not
13 // constexpr function
14
15 constexpr std::vector<int> v{1, 2, 3};
16 // ERROR: construction result is not constant expression since
17 // constructor of std::vector<int> allocates memory via new
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 310
Constexpr Function Requirements
■ constexpr function must satisfy following requirements: ⁓⁓⁓⁓⁓⁓⁓⁓
[C++20 §9.2.5/3]
[C++20 §9.2.5/6]
⁓⁓⁓⁓⁓⁓⁓⁓
2 must not be a coroutine
2 its return type (if any) must be literal type
2 each of its parameters must be of literal type
2 there exists at least one set of argument values such that invocation of
function could be evaluated expression of core constant expression
2 function body must be either deleted or defaulted or contain any statements
except:
2 goto statement
2 statement with label other than case and default
2 definition of variable of non-literal type
2 definition of variable of static or thread storage duration
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 311
Example: Constexpr Function Requirement Violations
1 #include <set>
2 #include <iostream>
3
4 // ERROR: return type not literal type
5 constexpr std::set<int> get_values()
6 {return std::set<int>{1, 2, 3};}
7
8 // ERROR: parameter type not literal type
9 constexpr void foo(std::set<int> s) { /* ... */ }
10
11 // ERROR: no argument exists such that function can be used
12 // in constant expression
13 constexpr void output(int i) {std::cout << i << ’\n’;}
14
15 constexpr void func() {
16 std::set<int> v{1, 2, 3};
17 // ERROR: definition of variable of non-literal type
18 // ...
19 }
20
21 constexpr int count() {
22 static unsigned int i = 0;
23 // ERROR: definition of variable with static storage
24 // duration
25 return i++;
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 312
Constexpr Constructor and Destructor Requirements
■ constexpr constructor must satisfy all requirements of constexpr function
and constructor’s class must not have any virtual base classes [C++20 §9.2.5/3]
⁓⁓⁓⁓⁓⁓⁓⁓
■ definition of constexpr constructor whose function body is not = delete
must additionally satisfy following requirements: ⁓⁓⁓⁓⁓⁓⁓⁓
[C++20 §9.2.5/4]
2 for non-delegating constructor, every constructor selected to initialize
nonstatic data members and base class subobjects must be constexpr
constructor
2 for delegating constructor, target constructor must be constexpr constructor
■ constexpr destructor must satisfy all requirements of constexpr function
and destructor’s class must not have any virtual base classes [C++20 §9.2.5/3]
⁓⁓⁓⁓⁓⁓⁓⁓
■ definition of constexpr destructor whose function body is not = delete
must additionally satifying following requirements: [C++20 §9.2.5/5]
⁓⁓⁓⁓⁓⁓⁓⁓
2 for every subobject of class type or (possibly multidimensional array
thereof) that class type must have constexpr destructor
■ constructors may be constinit, while destructors cannot be ⁓⁓⁓⁓⁓⁓⁓⁓
[C++20 §9.2.5/2]
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 313
Example: Constexpr Constructor Requirement Violations
1 #include <set>
2
3 class Widget {
4 public:
5 constexpr Widget(std::set<int> s) : i_(s.size()) {}
6 // ERROR: type of constructor parameter not literal type
7 // ...
8 private:
9 // only one data member
10 int i_;
11 };
12
13 // OK
14 class Base {
15 public:
16 Base(int i) : i_(i) {}
17 private:
18 int i_;
19 };
20
21 class Derived : public Base {
22 public:
23 constexpr Derived() : Base(42) {}
24 // ERROR: Base constructor not constexpr
25 // ...
26 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 314
Example: Constexpr Constructors and Destructor
1 #include <utility>
2
3 class widget {
4 public:
5 constexpr widget(int i = 0) : ip_{new int(i)} {}
6 constexpr ~widget() {delete ip_;}
7 constexpr widget(widget&& other) : ip_(nullptr)
8 {std::swap(ip_, other.ip_);}
9 constexpr widget& operator=(widget&& other) {
10 std::swap(ip_, other.ip_);
11 return *this;
12 }
13 constexpr int get() const {return *ip_;}
14 private:
15 int *ip_;
16 };
17
18 constexpr int func() {
19 widget w(42);
20 widget u(std::move(w));
21 w = std::move(u);
22 return w.get();
23 }
24
25 static_assert(func() == 42);
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 315
Example: Addresses and Constexpr
1 #include <cassert>
2
3 constexpr void func_1() {
4 char c = ’A’;
5 // c has automatic storage (i.e., on stack)
6 const char* p = &c;
7 // OK: address of c is well defined
8 assert(*p == ’A’); // OK
9 // constexpr const char* q = &c;
10 /* ERROR: &c not constant expression;
11 address of automatic object can be different
12 for each invocation of func_1 */
13 // above results also same if c is const or constexpr
14 }
15
16 static char sc = ’A’;
17 // sc has static storage (i.e., in program image)
18 constexpr void func_2() {
19 const char* p = ≻
20 // OK: address of sc is fixed and known at compile time
21 constexpr const char* q = ≻
22 /* OK: address of static object sc is fixed and known
23 at compile time */
24 // above results also same if sc is const or constexpr
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 316
Example: Pointers/References to Constexpr Objects
1 class Buffer {
2 public:
3 constexpr Buffer() : data_() {}
4 constexpr const char& operator[](unsigned int i) const
5 {return data_[i];}
6 constexpr char& operator[](unsigned int i)
7 {return data_[i];}
8 constexpr const char* data() const {return data_;}
9 // ...
10 private:
11 char data_[256];
12 };
13
14 int main() {
15 constexpr Buffer b; // OK
16 constexpr Buffer a = b; // OK
17 constexpr char c = b[0]; // OK
18 // constexpr const Buffer& br = b;
19 // ERROR: reference to b is not a constant expression
20 // constexpr const char& cr = b[0];
21 // ERROR: reference to subobject of b is not constant
22 // expression
23 // constexpr const char* cp = b.data();
24 // ERROR: pointer to subobject of b is not constant
25 // expression
26 // constexpr const Buffer* bp = &b;
27 // ERROR: pointer to b is not constant expression
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 317
Example: Constexpr and Accessing External State
1 // ERROR: static object not allowed in constexpr function
2 // (since function would have state the persists across
3 // invocations)
4 constexpr unsigned int get_count() {
5 static int count = 0;
6 return count++;
7 }
8
9 int global_count = 0;
10 // ERROR: constexpr function cannot modify state outside
11 // that function that might be used at run time
12 constexpr int get_global_count() {return global_count++;}
13
14 double alpha = 2.0;
15 // ERROR: constexpr function cannot access state outside
16 // that function that can be modified at run time.
17 constexpr double foo(double x) {return alpha * x + 3.0;}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 318
Debugging Constexpr Functions
■ debugging constexpr code can often be somewhat tricky
■ cannot generate output to assist in debugging (e.g., by writing to standard
output/error stream) since cannot perform I/O at compile time
■ use of source-level debugger not practical, since compiler would need to
be run in debugger
■ could first debug code without constexpr qualifier and then add constexpr
qualifier after code is working, but this may not be practical if code must
fundamentally execute at compile time (e.g., due to return value of
function being assigned to constexpr variable)
■ can use assert to test for conditions indicative of bugs (since
assert(expr) is constant expression if expr is true)
■ can throw exception if condition indicative of bug is detected (since still
constant expression as long as throw statement not executed)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 319
Example: Debugging Strategies for Constexpr Functions
1 #include <stdexcept>
2 #include <cassert>
3
4 constexpr double sqrt(double x) {
5 // if assertion fails, sqrt function will not yield
6 // constant expression
7 assert(x >= 0.0);
8 double result = 0.0;
9 // ... (correctly initialize result)
10 return result;
11 }
12
13 constexpr int foo(unsigned x) {
14 unsigned i = 0;
15 // ... (code that changes i)
16 // assume odd i indicative of bug
17 // if i is odd (which would result in exception
18 // being thrown), foo function will not yield
19 // constant expression
20 if (i & 1) {throw std::logic_error("i is odd");}
21 return 0;
22 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 320
Proxy Classes
■ proxy class provides modified interface to another class
■ classic example of proxy class is type returned by nonconst overload of
subscript operator in std::vector<bool> class
■ in this case, proxy type serves as stand-in for single bool element in
vector
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 321
Proxy Class Example: BoolVector
■ in this example, we consider simple container class called BoolVector
that provides dynamically-sized array of boolean values, where booleans
are packed into bytes
■ want to provide only very basic functionality for class:
2 member function for querying size of container
2 subscript operator for accessing elements in container
■ return type of nonconst overload of subscript operator is proxy type (called
Proxy) in order to handle fact that bits are packed into bytes
■ BoolVector is essentially greatly simplified version of
std::vector<bool>
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 322
Proxy Class Example: BoolVector.hpp
1 #include <cstddef>
2 #include <utility>
3
4 class BoolVector;
5
6 class Proxy {
7 public:
8 ~Proxy() = default;
9 Proxy& operator=(const Proxy&);
10 Proxy& operator=(bool b);
11 operator bool() const;
12 private:
13 friend class BoolVector;
14 Proxy(const Proxy&) = default;
15 Proxy(BoolVector* v, std::size_t i) : v_(v), i_(i) {}
16 BoolVector* v_;
17 std::size_t i_;
18 };
19
20 class BoolVector {
21 public:
22 BoolVector(std::size_t n) : n_(n), d_(new unsigned char[(n + 7) / 8]) {std::fill_n(d_, (n + 7) / 8, 0);}
23 ~BoolVector() {delete [] d_;}
24 std::size_t size() const {return n_;}
25 bool operator[](std::size_t i) const {return getElem(i);}
26 Proxy operator[](std::size_t i) {return Proxy(this, i);}
27 private:
28 friend class Proxy;
29 bool getElem(std::size_t i) const {return (d_[i / 8] >> (i % 8)) & 1;}
30 void setElem(std::size_t i, bool b) {(d_[i / 8] &= ~(1 << (i % 8))) |= (b << (i % 8));}
31 std::size_t n_;
32 unsigned char* d_;
33 };
34
35 inline Proxy& Proxy::operator=(const Proxy& other) {v_->setElem(i_, other); return *this;}
36 inline Proxy& Proxy::operator=(bool b) {v_->setElem(i_, b); return *this;}
37 inline Proxy::operator bool() const {return v_->getElem(i_);}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 323
Proxy Class Example: BoolVector.cpp
1 #include <cassert>
2 #include <iostream>
3 #include "BoolVector.hpp"
4
5 int main() {
6 constexpr int bits[] = {0, 0, 1, 1, 0, 1, 0, 1};
7 constexpr int n = sizeof(bits) / sizeof(int);
8 BoolVector v(n);
9 BoolVector w(n);
10 assert(v.size() == n && w.size() == n);
11 for (int i = 0; i < n; ++i) {
12 w[i] = v[i] = bits[i];
13 }
14 const BoolVector& cv = v;
15 for (int i = 0; i < n; ++i) {
16 assert(v[i] == bits[i]);
17 assert(w[i] == bits[i]);
18 assert(cv[i] == bits[i]);
19 std::cout << (v[i] ? ’1’ : ’0’);
20 }
21 std::cout << ’\n’;
22 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 324
Implementing Postfix Increment/Decrement Operator
■ often, good idea to implement postfix increment/decrement operator in
terms of prefix increment/decrement operator
■ ensures that prefix and postfix versions of operator always consistent
■ example:
1 class Counter {
2 public:
3 Counter(int count = 0) : count_(count) {}
4 Counter& operator++() {
5 ++count_;
6 return *this;
7 }
8 Counter operator++(int) {
9 Counter old(*this);
10 ++(*this);
11 return old;
12 }
13 // similarly for prefix/postfix decrement
14 private:
15 int count_;
16 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 325
Pointer-to-Implementation (Pimpl) Idiom
■ pointer to implementation (pimpl) idiom splits interface and
implementation across two classes, namely, handle class and
implementation class
■ all implementation details placed in implementation class
■ handle class provides only interface functions which simply forward calls
through to implementation class
■ handle object has pointer that owns implementation object
■ only handle class is exposed to client
■ consequently, changes to implementation class do not require client code
to be recompiled (since interface has not changed)
■ thus, pimpl idiom useful for reducing compile-time dependencies (which
can facilitate faster compiles) and maintaining stable class ABIs
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 326
Pimpl and std::experimental::propagate_const
1 #include <experimental/propagate_const>
2 #include <memory>
3 #include <iostream>
4
5 class WidgetImpl {
6 public:
7 void foo() {std::cout << "WidgetImpl::foo()\n";}
8 void foo() const {std::cout << "WidgetImpl::foo() const\n";}
9 };
10
11 class Widget {
12 public:
13 void foo() {p_->foo();}
14 void foo() const {p_->foo();}
15 private:
16 std::experimental::propagate_const<std::unique_ptr<WidgetImpl>> p_;
17 /* const or non-const member functions of WidgetImpl invoked
18 as appropriate based on constness of *this; using
19 std::unique_ptr<const WidgetImpl> would cause only const member
20 functions of WidgetImpl to be invoked; using
21 std::unique_ptr<WidgetImpl> would cause only non-const member
22 functions of WidgetImpl to be invoked */
23 };
24
25 int main() {
26 Widget w;
27 const Widget cw;
28 w.foo(); // calls WidgetImpl:foo()
29 cw.foo(); // calls WidgetImpl:foo() const
30 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 327
Section 2.4.5
Functors
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 328
Functors
■ function object (also known as functor) is object that can be invoked or
called as if it were ordinary function
■ class that provides member function that overloads operator() is
called functor class and object of that class is functor
■ functors more flexible than functions as functors are objects and can
therefore carry arbitrary state information
■ when ordinary function used, function often invoked through pointer
whose value cannot be determined at compile time, which makes inlining
impossible
■ when functor used, function to be called is fixed and always known at
compile time (namely, function-call operator for functor class)
■ moreover, definition of function-call operator very likely to be visible at
point of use, especially if functor created from lambda expression
■ consequently, functors often more amenable to inlining
■ functors are extremely useful, especially in generic programming
■ as we will see later, standard library makes heavy use of functors
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 329
Functor Example: Less Than
1 struct LessThan { // Functor class
2 bool operator()(double x, double y) const {
3 return x < y;
4 }
5 };
6
7 void myFunc() {
8 double a = 1.0;
9 double b = 2.0;
10 LessThan lessThan; // Functor
11 bool result = lessThan(a, b);
12 // calls LessThan::operator()(double, double)
13 // lessThan is functor, not function
14 // result == true
15 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 330
Functor Example With State
1 class IsGreater { // Functor class
2 public:
3 IsGreater(int threshold) : threshold_(threshold) {}
4 bool operator()(int x) const {
5 return x > threshold_;
6 }
7 private:
8 // state information for functor
9 int threshold_; // threshold for comparison
10 };
11
12 void myFunc() {
13 IsGreater isGreater(5); // functor
14 int x = 3;
15 bool result = isGreater(x);
16 // calls IsGreater::operator()(int)
17 // result == false
18 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 331
Ordering Relations
■ often, need arises to impose some ordering on data
■ for example, ordering relation needed for any sorting algorithm or ordered
container (such as ordered set, multiset, map, or multimap)
■ to define ordering relation, sufficient to specify either less-than (i.e.,
“precedes”) relation or greater-than (i.e., “follows”) relation
■ typically, in C++ (such as in standard library) less-than relation is used to
define all other relational operators
■ less(x, y) is true if x precedes y in sorted order and false otherwise
■ can synthesize all other relational operators from less as follows:
2 greater(x, y) = less(y, x)
2 equal(x, y) = ¬less(x, y) ∧ ¬less(y, x)
2 notEqual(x, y) = less(x, y) ∨ less(y, x)
2 lessEqual(x, y) = ¬less(y, x)
2 greaterEqual(x, y) = ¬less(x, y)
■ note: “¬” denotes logical NOT, “∧” denotes logical AND, and “∨” denotes
logical OR
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 332
Selection Sort Example
1 #include <algorithm>
2 #include <cassert>
3 #include <forward_list>
4 #include <functional>
5 #include <iterator>
6
7 // reverse digits in decimal representation of integer
8 constexpr unsigned int reverse(unsigned int x) {
9 unsigned int y = 0;
10 for (; x; x /= 10) {auto d = x % 10; x -= d; y = 10 * y + d;}
11 return y;
12 }
13
14 constexpr bool rev_less(unsigned int x, unsigned int y)
15 {return reverse(x) < reverse(y);}
16
17 template <std::forward_iterator I, class Compare>
18 void selection_sort(I first, I last, Compare less) {
19 for (auto i = first; i != last; ++i)
20 {std::iter_swap(i, std::min_element(i, last, less));}
21 }
22
23 int main() {
24 std::forward_list<unsigned int> values{12, 21, 123, 321, 1234, 4321};
25 selection_sort(values.begin(), values.end(), std::greater<unsigned int>());
26 assert((values == std::forward_list<unsigned int>{
27 4321, 1234, 321, 123, 21, 12}));
28 selection_sort(values.begin(), values.end(), rev_less);
29 assert((values == std::forward_list<unsigned int>{
30 21, 12, 321, 123, 4321, 1234}));
31 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 333
Bubble Sort Example
1 #include <algorithm>
2 #include <cassert>
3 #include <forward_list>
4 #include <functional>
5 #include <iterator>
6
7 template <std::forward_iterator I, class Compare>
8 void bubble_sort(I first, I last, Compare less) {
9 for (auto sorted = first; first != last; last = sorted) {
10 sorted = first;
11 for (auto cur = first, prev = first; ++cur != last; ++prev) {
12 if (less(*cur, *prev)) {
13 std::iter_swap(cur, prev);
14 sorted = cur;
15 }
16 }
17 }
18 }
19
20 int main() {
21 std::forward_list<int> values{7, 0, 6, 1, 5, 2, 4, 3};
22 bubble_sort(values.begin(), values.end(), std::less<int>());
23 assert((values == std::forward_list<int>{0, 1, 2, 3, 4, 5, 6, 7}));
24 bubble_sort(values.begin(), values.end(), std::greater<int>());
25 assert((values == std::forward_list<int>{7, 6, 5, 4, 3, 2, 1, 0}));
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 334
Comparison Object Example
1 #include <cassert>
2 #include <algorithm>
3 #include <set>
4
5 template <class T> class compare {
6 public:
7 constexpr compare(bool less = true) : less_(less) {}
8 constexpr bool operator()(const T& x, const T& y) const
9 {return less_ ? (x < y) : (x > y);}
10 private:
11 bool less_;
12 };
13
14 constexpr bool even_then_odd(int x, int y)
15 {if ((x % 2) != (y % 2)) {return !(x % 2);} else {return x < y;}}
16
17 int main() {
18 constexpr int values[] = {0, 7, 6, 1, 2, 5, 3, 4};
19 std::set<int, compare<int>> s1(std::begin(values), std::end(values));
20 constexpr int d1[] = {0, 1, 2, 3, 4, 5, 6, 7};
21 assert(std::equal(s1.begin(), s1.end(), std::begin(d1)));
22 std::set<int, compare<int>> s2(std::begin(values), std::end(values),
23 compare<int>(false));
24 constexpr int d2[] = {7, 6, 5, 4, 3, 2, 1, 0};
25 assert(std::equal(s2.begin(), s2.end(), std::begin(d2)));
26 std::set<int, bool (*)(int, int)> s3(std::begin(values), std::end(values),
27 even_then_odd);
28 constexpr int d3[] = {0, 2, 4, 6, 1, 3, 5, 7};
29 assert(std::equal(s3.begin(), s3.end(), std::begin(d3)));
30 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 335
Comparison Object Propagation
■ invariant of ordered container: elements of container always sorted with
respect to ordering relation defined by comparison object
■ thus, state for ordered container (ignoring possible allocator) consists of:
1 elements in container; and
2 comparison object that determines order of those elements
■ consider propagating value of one container to another (via copy or move)
■ when propagating value of container, two choices possible:
1 propagate comparison object
2 do not propagate comparison object
■ if comparison object not propagated and source and destination
comparison objects differ, must re-sort elements (to be consistent with
destination comparison object) to avoid violating container invariant
■ if no equality/inequality operator provided by comparison-object type,
must assume worst (i.e., not equal) and always re-sort
■ if comparison object propagated, never any need to re-sort elements
■ for efficiency, prefer solution of always propagating comparison object
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 336
Comparison Object Propagation Example
1 #include <algorithm>
2 #include <cassert>
3 #include <set>
4 #include <utility>
5
6 template <class T> class compare {
7 public:
8 compare(bool less = true) : less_(less) {}
9 bool operator()(const T& x, const T& y) const
10 {return less_ ? (x < y) : (x > y);}
11 bool less() const {return less_;}
12 private:
13 bool less_;
14 };
15
16 int main() {
17 constexpr int values[] = {0, 7, 6, 1, 2, 5, 3, 4};
18 std::set<int, compare<int>> s3(values, std::end(values));
19 std::set<int, compare<int>> s1(std::move(s3)); // move construct
20 assert(s1.key_comp().less()); // comparison object was moved
21 std::set<int, compare<int>> s2(s1); // copy construct
22 assert(s2.key_comp().less()); // comparison object was copied
23 s3 = std::set<int, compare<int>>(values, std::end(values),
24 compare<int>(false));
25 assert(s1.key_comp().less() && !s3.key_comp().less());
26 s1 = std::move(s3); // move assign
27 assert(!s1.key_comp().less()); // comparison object was moved
28 assert(s2.key_comp().less() && !s1.key_comp().less());
29 s2 = s1; // copy assign
30 assert(!s2.key_comp().less()); // comparison object was copied
31 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 337
Section 2.4.6
References
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 338
Talks I
1 Arthur O’Dwyer. Return Value Optimization: Harder Than It Looks.
CppCon, Bellevue, WA, USA, Sept. 25, 2018. Available online at
https://youtu.be/hA1WNtNyNbo.
2 Jon Kalb. Copy Elision. C++Now, Aspen, CO, USA, May 9, 2018.
Available online at https://youtu.be/fSB57PiXpRw.
3 Jon Kalb. Copy Elision. CppCon, Bellevue, WA, USA, Sept. 23–28, 2018.
Available online at https://youtu.be/IZbL-RGr_mk.
4 Roger Orr. Nothing Is Better Than Copy Or Move. ACCU Conference,
Bristol, UK, Apr. 11, 2018. Available online at
https://youtu.be/-dc5vqt2tgA.
5 Scott Schurr. constexpr: Introduction. CppCon, Bellevue, WA, USA, Sept
19–25, 2015. Available online at https://youtu.be/fZjYCQ8dzTc.
6 Scott Schurr. constexpr: Applications. CppCon, Bellevue, WA, USA, Sept
19–25, 2015. Available online at https://youtu.be/qO-9yiAOQqc.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 339
Section 2.5
Templates
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 340
Templates
■ generic programming: algorithms written in terms of types to be
specified later (i.e., algorithms are generic in sense of being applicable to
any type that meets only some very basic constraints)
■ templates facilitate generic programming
■ extremely important language feature
■ avoids code duplication
■ leads to highly efficient and customizable code
■ promotes code reuse
■ C++ standard library makes very heavy use of templates (actually, most of
standard library consists of templates)
■ many other libraries make heavy use of templates (e.g., CGAL, Boost)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 341
Section 2.5.1
Function Templates
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 342
Motivation for Function Templates
■ consider following functions:
int max(int x, int y)
{return x > y ? x : y;}
double max(double x, double y)
{return x > y ? x : y;}
// more similar-looking max functions...
■ each of above functions has same general form; that is, for some type T,
we have:
T max(T x, T y)
{return x > y ? x : y;}
■ would be nice if we did not have to repeatedly type, debug, test, and
maintain nearly identical code
■ in effect, would like code to be parameterized on type T
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 343
Function Templates
■ function template is family of functions parameterized by one or more
parameters
■ each template parameter can be: non-type (e.g., integral constant), type,
template, or parameter pack (in case of variadic template)
■ syntax for template function has general form:
template <parameter_list> function_declaration
■ parameter_list: parameters on which template function depends
■ function_declaration: function declaration or definition
■ type parameter designated by class or typename keyword
■ template parameter designated by template keyword
■ non-type parameter designed by its type (e.g., bool, int)
■ example:
// declaration of function template
template <class T> T max(T x, T y);
// definition of function template
template <class T> T max(T x, T y)
{return x > y ? x : y;}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 344
Function Templates (Continued)
■ to explicitly identify particular instance of template, use syntax:
function<parameters>
■ example: for function template declaration:
template <class T> T max(T x, T y);
max<int> refers to int max(int, int)
max<double> refers to double max(double, double)
■ compiler only creates code for function template when it is instantiated
(i.e., used)
■ therefore, definition of function template must be visible in place where it
is instantiated
■ consequently, function template definitions usually appear in header file
■ template code only needs to pass basic syntax checks, unless actually
instantiated
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 345
Function Template Examples
1 // compute minimum of two values
2 template <class T>
3 T min(T x, T y) {
4 return x < y ? x : y;
5 }
6
7 // compute square of value
8 template <typename T>
9 T sqr(T x) {
10 return x * x;
11 }
12
13 // swap two values
14 template <class T>
15 void swap(T& x, T& y) {
16 T tmp = x;
17 x = y;
18 y = tmp;
19 }
20
21 // invoke function/functor multiple times
22 template <int N = 1, typename F, typename T>
23 void invoke(F func, const T& value) {
24 for (int i = 0; i < N; ++i) {
25 func(value);
26 }
27 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 346
Template Function Overload Resolution
■ overload resolution proceeds (in order) as follows:
1 look for an exact match with zero or more trivial conversions on
(nontemplate) functions; if found call it
2 look for function template from which function that can be called with exact
match with zero or more trivial conversions can be generated; if found, call it
3 try ordinary overload resolution for functions; if function found, call it;
otherwise, call is error
■ in each step, if more than one match found, call is ambiguous and is error
■ template function only used in case of exact match, unless explicitly forced
■ example:
template <class T>
T max(T x, T y) {return x > y ? x : y;}
void func(int i, int j, double x, double y) {
double z = max(x, y); // calls max<double>
int k = max(i, j); // calls max<int>
z = max(i, x); // ERROR: no match
z = max<double>(i, x); // calls max<double>
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 347
Qualified Names
■ qualified name is name that specifies scope
■ example:
#include <iostream>
int main(int argc, char** argv) {
for (int i = 0; i < 10; ++i) {
std::cout << "Hello, world!" << std::endl;
}
}
■ in above example, names std::cout and std::endl are qualified, while
names main, argc, argv, and i, are not qualified
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 348
Dependent Names
■ dependent name is name that depends on template parameter
[C++17 §17.6.2.1/9]
⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ example:
template <class T>
void func(const T& x) {
int i = T::magicValue;
// ...
}
■ name T::magicValue is dependent
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 349
Qualified Dependent Names
■ to avoid any potential ambiguities, compiler will automatically assume
qualified dependent name does not name type unless typename
keyword is used
■ must precede qualified dependent name that names type by typename
■ in following example, note use of typename keyword:
1 #include <vector>
2
3 template <class T>
4 void func(const T& x) {
5 std::vector<T> v(42, x);
6 // std::vector<T>::const_iterator is
7 // qualified dependent name
8 for (typename std::vector<T>::const_iterator i =
9 v.begin(); i != v.end(); ++i) {
10 // std::vector<T>::value_type is
11 // qualified dependent name
12 typename std::vector<T>::value_type x = *i;
13 // ...
14 }
15 // ...
16 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 350
Why typename is Needed
1 int x = 42;
2
3 template <class T> void func() {
4 /* The compiler must be able to check the syntactic
5 correctness of this template code without knowing the
6 type T. Without knowing the type T, however, the meaning
7 of the following line of code is ambiguous, unless the
8 compiler follows some fixed rule for resolving this
9 ambiguity. In particular, is this line of code a
10 declaration of a variable x or an expression consisting
11 of a binary operator* with operands T::foo and x? */
12 T::foo* x; // Does T::foo name a type or an object?
13 }
14
15 struct ContainsType {
16 using foo = int; // foo is type
17 };
18
19 struct ContainsValue {
20 static int foo; // foo is value
21 };
22
23 int main() {
24 // Only one of the following two lines should be valid.
25 func<ContainsValue>();
26 func<ContainsType>();
27 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 351
Section 2.5.2
Class Templates
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 352
Motivation for Class Templates
■ consider almost identical complex number classes:
1 class ComplexDouble {
2 public:
3 ComplexDouble(double x = 0.0, double y = 0.0) : x_(x), y_(y) {}
4 double real() const { return x_; }
5 double imag() const { return y_; }
6 // ...
7 private:
8 double x_, y_; // real and imaginary parts
9 };
10
11 class ComplexFloat {
12 public:
13 ComplexFloat(float x = 0.0f, float y = 0.0f) : x_(x), y_(y) {}
14 float real() const { return x_; }
15 float imag() const { return y_; }
16 // ...
17 private:
18 float x_, y_; // real and imaginary parts
19 };
■ both of above classes are special cases of following class parameterized
on type T:
1 class Complex {
2 public:
3 Complex(T x = T(0), T y = T(0)) : x_(x), y_(y) {}
4 T real() const { return x_; }
5 T imag() const { return y_; }
6 // ...
7 private:
8 T x_, y_; // real and imaginary parts
9 };
■ again, would be nice if we did not have to repeatedly type, debug, test,
and maintain nearly identical code
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 353
Class Templates
■ class template is family of classes parameterized on one or more
parameters
■ each template parameter can be: non-type (e.g., integral constant), type,
template, or parameter pack (in case of variadic template)
■ syntax has general form:
template <parameter_list> class_declaration
■ parameter_list: parameter list for class
■ class_declaration: class/struct declaration or definition
■ example:
// declaration of class template
template <class T, unsigned int size>
class MyArray;
// definition of class template
template <class T, unsigned int size>
class MyArray {
// ...
T array_[size];
};
MyArray<double, 100> x;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 354
Class Templates (Continued)
■ compiler only generates code for class template when it is instantiated
(i.e., used)
■ since compiler only generates code for class template when it is
instantiated, definition of template must be visible at point where
instantiated
■ consequently, class template code usually placed in header file
■ template code only needs to pass basic syntax checks, unless actually
instantiated
■ compile errors related to class templates can often be very long and
difficult to parse (especially, when template class has parameters that are
template classes which, in turn, have parameters that are template
classes, and so on)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 355
Class Template Example
1 // complex number class template
2 template <class T>
3 class Complex {
4 public:
5 Complex(T x = T(0), T y = T(0)) :
6 x_(x), y_(y) {}
7 T real() const {
8 return x_;
9 }
10 T imag() const {
11 return y_;
12 }
13 // ...
14 private:
15 T x_; // real part
16 T y_; // imaginary part
17 };
18
19 Complex<int> zi;
20 Complex<double> zd;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 356
Class-Template Default Parameters
■ class template parameters can have default values
■ example:
template <class T = int, unsigned int size = 2>
struct MyArray {
T data[size];
};
MyArray<> a; // MyArray<int, 2>
MyArray<double> b; // MyArray<double, 2>
MyArray<double, 10> b; // MyArray<double, 10>
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 357
Qualified Dependent Names Revisited
■ recall, qualified dependent name assumed not to name type, unless
preceded by typename keyword
■ in following example, note use of typename keyword:
1 #include <vector>
2
3 template <class T> class Vector {
4 public:
5 using Coordinate = typename T::Coordinate;
6 using Distance = typename T::Distance;
7 Vector(const std::vector<Coordinate>& coords) :
8 coords_(coords) {}
9 Distance squaredLength() const {
10 Distance d = Distance(0);
11 for (typename
12 std::vector<Coordinate>::const_iterator i =
13 coords_.begin(); i != coords_.end(); ++i) {
14 typename std::vector<Coordinate>::value_type
15 x = *i;
16 d += x * x;
17 }
18 return d;
19 }
20 // ...
21 private:
22 std::vector<Coordinate> coords_;
23 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 358
Why template is Needed
1 template<bool> struct Widget;
2
3 template<bool B> struct Gadget {
4 static int g() {
5 /* The compiler must be able to check the syntactic
6 correctness of this template code without knowing the
7 value of B. Without knowing the value of B, however,
8 the meaning of the following line of code is ambiguous,
9 unless the compiler follows some fixed rule for
10 resolving this ambiguity. In particular, is this line
11 of code using a data member called f and evaluating
12 (f < 0 > 42) or is it calling a template member
13 function called f with the argument 42? */
14 return Widget<B>::f<0>(42);
15 }
16 };
17
18 template<bool B> struct Widget {
19 template<int I> static int f(int i) {return i + I;}
20 };
21
22 template<> struct Widget<false> {inline static int f = 42;};
23
24 int main() {
25 // Only one of the following two lines should be valid.
26 Gadget<true>::g();
27 Gadget<false>::g();
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 359
Template Template Parameter Example
1 #include <vector>
2 #include <list>
3 #include <deque>
4 #include <memory>
5
6 template <template <class, class> class Container, class Value>
7 class Stack {
8 public:
9 // ...
10 private:
11 Container<Value, std::allocator<Value>> data_;
12 };
13
14 int main() {
15 Stack<std::vector, int> s1;
16 Stack<std::list, int> s2;
17 Stack<std::deque, int> s3;
18 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 360
Class Template Parameter Deduction
■ template parameters for class template can be deduced based on
arguments passed to constructor
■ example:
std::tuple t(42, ’A’);
// OK: deduced as tuple<int, char>
■ deduction only performed if no template arguments provided
■ example:
std::tuple<int> t(1, 2);
// ERROR: missing template parameter, as
// no template parameter deduction takes place
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 361
Class Template Parameter Deduction Example
1 #include <vector>
2 #include <tuple>
3 #include <set>
4 #include <string>
5
6 using namespace std::string_literals;
7
8 auto get_tuple() {
9 return std::tuple("Zaphod"s, 42);
10 // deduces tuple<std::string, int>
11 }
12
13 int main() {
14 std::vector v{1, 2, 3};
15 // deduces vector<int>
16 std::tuple t(true, ’A’, 42);
17 // deduces tuple<bool, char, int>
18 std::pair p(42, "Hello"s);
19 // deduces pair<int, std::string>
20 std::set s{0.5, 0.25};
21 // deduces set<double>
22 //auto ptr = new std::tuple(true, 42);
23 // should deduce tuple<bool, int>?
24 // fails to compile with GCC 7.1.0
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 362
Template Deduction Guides
■ can provide additional rules to be used to determine how class template
parameters should be deduced when not provided
■ such rules called deduction guides
■ deduction guide itself can be either template or non-template
■ deduction guides must be introduced in same scope as class template
■ example:
// class definition
template <class T> smart_ptr {/* ... */};
// deduction guide
template <class T>
smart_ptr(T*) -> smart_ptr<T>;
■ example:
/// class definition
template <class T> name {/* ... */};
// deduction guide
name(const char*) -> name<std::string>;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 363
Template Deduction Guide Example
1 #include <string>
2 #include <type_traits>
3
4 using namespace std::string_literals;
5
6 template <class T>
7 class Name {
8 public:
9 Name(T first, T last) : first_(first), last_(last) {}
10 // ...
11 private:
12 T first_;
13 T last_;
14 };
15
16 // deduction guide
17 Name(const char*, const char*) -> Name<std::string>;
18
19 int main() {
20 Name n("Zaphod", "Beeblebrox");
21 // deduces Name<std::string> via deduction guide
22 static_assert(std::is_same_v<decltype(n), Name<std::string>>);
23 Name n2("Jane"s, "Doe"s);
24 // deduces Name<std::string> (without deduction guide)
25 static_assert(std::is_same_v<decltype(n2), Name<std::string>>);
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 364
Auto Non-Type Template Parameters
■ can use auto keyword for non-type template parameter
■ in such case, type of non-type template parameter will be deduced
■ example:
template <auto v>
struct constant {
static constexpr decltype(v) value = v;
};
using forty_two_type = constant<42>;
// template parameter v deduced to have type int
■ non-type template parameter type deduction probably most useful for
template metaprogramming
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 365
Example Without Auto Non-Type Template Parameter
1 #include <cstdlib>
2 #include <iostream>
3
4 template<class T, T v>
5 struct integral_constant {
6 using value_type = T;
7 static constexpr value_type value = v;
8 using type = integral_constant;
9 constexpr operator value_type() const noexcept
10 {return value;}
11 constexpr value_type operator()() const noexcept
12 {return value;}
13 };
14
15 using forty_two_type = integral_constant<int, 42>;
16
17 int main() {
18 constexpr forty_two_type x;
19 constexpr auto v = x.value;
20 std::cout << v << ’\n’;
21 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 366
Example With Auto Non-Type Template Parameter
1 #include <cstdlib>
2 #include <iostream>
3
4 template<auto v>
5 struct integral_constant {
6 using value_type = decltype(v);
7 static constexpr value_type value = v;
8 using type = integral_constant;
9 constexpr operator value_type() const noexcept
10 {return value;}
11 constexpr value_type operator()() const noexcept
12 {return value;}
13 };
14
15 using forty_two_type = integral_constant<42>;
16
17 int main() {
18 constexpr forty_two_type x;
19 constexpr auto v = x.value;
20 std::cout << v << ’\n’;
21 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 367
Section 2.5.3
Variable Templates
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 368
Variable Templates
■ variable template is family of variables parameterized on one or more
parameters
■ each template parameter can be: non-type (e.g., integral constant), type,
template, or parameter pack (in case of variadic templates)
■ although less frequently used than function and class templates, variable
templates quite useful in some situations
■ syntax has general form:
template <parameter_list> variable_declaration
■ parameter_list: parameter list for variable template
■ variable_declaration: variable declaration or definition
■ example:
template <class T>
T meaning_of_life = T(42);
int x = meaning_of_life<int>;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 369
Variable Template Example: pi
1 #include <limits>
2 #include <complex>
3 #include <iostream>
4
5 template <typename T>
6 constexpr T pi =
7 T(3.14159265358979323846264338327950288419716939937510L);
8
9 int main() {
10 std::cout.precision(
11 std::numeric_limits<long double>::max_digits10);
12 std::cout
13 << pi<int> << ’\n’
14 << pi<float> << ’\n’
15 << pi<double> << ’\n’
16 << pi<long double> << ’\n’
17 << pi<std::complex<float>> << ’\n’
18 << pi<std::complex<double>> << ’\n’
19 << pi<std::complex<long double>> << ’\n’;
20 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 370
Section 2.5.4
Alias Templates
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 371
Alias Templates
■ alias template is family of types parameterized on one or more
parameters
■ each template parameter can be: non-type (e.g., integral constant), type,
template, or parameter pack (in case of variadic templates)
■ syntax has general form:
template <parameter_list> alias_declaration
■ parameter_list: parameter list for class
■ alias_declaration: alias declaration (i.e., with using)
■ example:
template <class Value,
class Alloc = std::allocator<Value>>
using GreaterMultiSet =
std::multiset<Value, std::greater<Value>, Alloc>;
GreaterMultiSet<int> x{4, 1, 3, 2};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 372
Alias Template Example
1 #include <iostream>
2 #include <set>
3
4 // alias template for set that employs std::greater for
5 // comparison
6 template <typename Value,
7 typename Alloc = std::allocator<Value>>
8 using GreaterSet = std::set<Value,
9 std::greater<Value>, Alloc>;
10
11 int main() {
12 std::set x{1, 4, 3, 2};
13 GreaterSet<int> y{1, 4, 3, 2};
14 for (auto i : x) {
15 std::cout << i << ’\n’;
16 }
17 std::cout << ’\n’;
18 for (auto i : y) {
19 std::cout << i << ’\n’;
20 }
21 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 373
Section 2.5.5
Variadic Templates
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 374
Variadic Templates
■ language provides ability to specify template that can take variable
number of arguments
■ template that can take variable number of arguments called variadic
template
■ alias templates, class templates, function templates, and variable
templates may be variadic
■ variable number of arguments specified by using what is called parameter
pack
■ parameter pack is parameter that accepts (i.e., is placeholder for) zero or
more arguments (of same kind)
■ parameter pack used in parameter list of template to allow to variable
number of template parameters
■ ellipsis (i.e., “...”) is used in various contexts relating to parameter packs
■ ellipsis after designator for kind of template argument in template
parameter list designates argument is parameter pack
■ ellipsis after parameter pack parameter expands parameter pack in
context-sensitive manner
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 375
Parameter Packs
■ syntax for non-type template parameter pack named Args and containing
elements of type type (e.g., bool, int, unsigned int):
type... Args
■ example:
template <int... Is> /* ... */
Is is (non-type) template parameter pack that corresponds to zero or
more (compile-time constant) values of type int
■ syntax for type template parameter pack named Args:
typename... Args
or equivalently
class... Args
■ examples:
template <typename... Ts> /* ... */
template <class... Ts> /* ... */
Ts is (type) template parameter pack that corresponds to zero or more
types
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 376
Parameter Packs (Continued 1)
■ syntax for template template parameter pack named Args:
template <parameter_list> typename... Args
or equivalently
template <parameter_list> class... Args
■ example:
template <template <class T> class... Ts>
/* ... */
Ts is (template) template parameter pack that corresponds to zero or
more templates
■ syntax for function parameter pack named args whose elements have
types corresponding to elements of type template parameter pack Args:
Args... args
■ example:
template <class... Ts> void func(Ts... args);
args is (function) parameter pack that corresponds to zero or more
function parameters whose types correspond to elements of type
parameter pack Ts
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 377
Parameter Packs (Continued 2)
■ in context where template arguments cannot be deduced (e.g., primary
class templates), only last template parameter can be parameter pack
■ in context where template arguments can be deduced (e.g., function
templates and class template partial specializations), template parameter
pack need not be last template parameter
■ example:
1 template <class U, class... Ts> class C1 { /* ... */ };
2 // OK: Ts is last template parameter
3
4 template <class... Ts, class U> class C2 { /* ... */ };
5 // ERROR: Ts not last and U not deduced
6
7 template <class... Ts, class U> void f1(Ts... ts)
8 { /* ... */ } // NOT OK: Ts not last and U not deduced
9
10 template <class... Ts, class U> void f2(Ts... ts, U u)
11 { /* ... */ } // OK: Ts not last but U is deduced
12
13 int main() {
14 f1<int, int, bool>(1, 2, true);
15 // ERROR: no matching function call
16 f2<int, int>(1, 2, true); // OK
17 f2(1, 2, true); // ERROR: one argument expected
18 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 378
Parameter Pack Expansion
■ parameter pack expansion: expands pack into its constituent elements
■ syntax for parameter pack expansion of expression pattern, which must
contain parameter pack:
pattern...
■ example:
1 template <class... Ts> void f(Ts... t) { /* ... */ }
2
3 template <class... Us> void g(Us... u) {
4 f(u...);
5 // u... is pack expansion
6 // when g is called by main,
7 // u... expands to 1, 2.0, 3.0f
8 }
9
10 int main() {
11 g(1, 2.0, 3.0f);
12 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 379
Variadic Template Examples
1 #include <tuple>
2
3 // variadic alias template
4 template <class... T>
5 using My_tuple = std::tuple<bool, T...>;
6
7 // variadic class template
8 template <int... Values>
9 class Integer_sequence {
10 // ...
11 };
12
13 // variadic function template
14 template <class... Ts>
15 void print(const Ts&... values) {
16 // ...
17 }
18
19 // variadic variable template
20 template <typename T, T... Values>
21 constexpr T array[] = {Values...};
22
23 int main() {
24 Integer_sequence<1, 3, 4, 2> x;
25 auto a = array<int, 1, 2, 4, 8>;
26 My_tuple<int, double> t(true, 42, 42.0);
27 print(1’000’000, 1, 43.2, "Hello");
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 380
Parameter Pack Expansion
■ parameter pack expansion allowed in following contexts: [C++17 §17.5.3/4]
⁓⁓⁓⁓⁓⁓⁓⁓
2 inside parentheses of function call operator
2 in template argument list
2 in function parameter list
2 in template parameter list
2 base class specifiers in class declaration
2 member initializer lists
2 braced initializer lists
2 lambda captures
2 fold expressions
2 in using declarations
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 381
The sizeof... Operator
■ sizeof... operator yields number of elements in parameter pack
■ example:
template <int... Values>
constexpr int num_parms = sizeof...(Values);
static_assert(num_parms<1, 2, 3> == 3);
static_assert(num_parms<> == 0);
■ example:
#include <cassert>
template <typename... Ts>
int number_of_arguments(const Ts&... args) {
return sizeof...(args);
}
int main() {
assert(number_of_arguments(1, 2, 3) == 3);
assert(number_of_arguments() == 0);
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 382
Variadic Function Template: sum
1 #include <iostream>
2 #include <string>
3
4 using namespace std::string_literals;
5
6 template <class T>
7 auto sum(T x) {
8 return x;
9 }
10
11 template <class T, class... Args>
12 auto sum(T x, Args... args) {
13 return x + sum(args...);
14 }
15
16 int main() {
17 auto x = sum(42.5, -1.0, 0.5f);
18 auto y = sum("The "s, "answer "s, "is "s);
19 std::cout << y << x << ".\n";
20 // sum(); // ERROR: no matching function call
21 }
22
23 /* Output:
24 The answer is 42.
25 */
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 383
Variadic Function Template: maximum
1 #include <type_traits>
2 #include <string>
3 #include <cassert>
4
5 using namespace std::string_literals;
6
7 template <typename T>
8 T maximum(const T& a) {return a;}
9
10 template <typename T1, typename T2>
11 typename std::common_type_t<const T1&, const T2&>
12 maximum(const T1 &a, const T2 &b) {
13 return a > b ? a : b;
14 }
15
16 template <typename T1, typename T2, typename... Args>
17 typename std::common_type_t<const T1&, const T2&,
18 const Args&...>
19 maximum(const T1& a, const T2& b, const Args&... args) {
20 return maximum(maximum(a, b), args...);
21 }
22
23 int main() {
24 assert(maximum(1) == 1);
25 assert(maximum(1, 2, 3, 4, -1.4) == 4);
26 assert(maximum(-1’000’000L, -42L, 10, 42.42) == 42.42);
27 assert(maximum("apple"s, "zebra"s, "c++"s) == "zebra"s);
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 384
Variadic Function Template With Template Template
Parameter: print_container
1 #include <iostream>
2 #include <vector>
3 #include <string>
4 #include <set>
5
6 template <template <class, class...>
7 class ContainerType, class ValueType, class... Args>
8 bool print_container(const ContainerType<ValueType, Args...>&
9 c) {
10 for (auto i = c.begin(); i != c.end();) {
11 std::cout << *i;
12 if (++i != c.end()) {std::cout << ’ ’;}
13 }
14 std::cout << ’\n’;
15 return bool(std::cout);
16 }
17
18 int main() {
19 using namespace std::string_literals;
20 std::vector vi{1, 2, 3, 4, 5};
21 std::set si{5, 4, 3, 2, 1};
22 std::set ss{"world"s, "hello"s};
23 print_container(vi);
24 print_container(si);
25 print_container(ss);
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 385
Variadic Class Template: Integer_sequence
1 #include <iostream>
2 #include <cstdlib>
3
4 template <class T, T... Values>
5 class Integer_sequence {
6 public:
7 using value_type = T;
8 using const_iterator = const T*;
9 constexpr std::size_t size() const
10 {return sizeof...(Values);}
11 constexpr T operator[](int i) const {return values_[i];}
12 constexpr const_iterator begin() const
13 {return &values_[0];}
14 constexpr const_iterator end() const
15 {return &values_[size()];}
16 private:
17 static constexpr T values_[sizeof...(Values)] =
18 {Values...};
19 };
20
21 template <class T, T... Values>
22 constexpr T
23 Integer_sequence<T, Values...>::values_[sizeof...(Values)];
24
25 int main() {
26 Integer_sequence<std::size_t, 1, 2, 4, 8> seq;
27 std::cout << seq.size() << ’\n’ << seq[0] << ’\n’;
28 for (auto i : seq) {std::cout << i << ’\n’;}
29 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 386
Variadic Variable Template: int_array
1 #include <iostream>
2
3 template <int... Args>
4 constexpr int int_array[] = {Args...};
5
6 int main() {
7 for (auto i : int_array<1,2,4,8>) {
8 std::cout << i << ’\n’;
9 }
10 }
11
12 /* Output:
13 1
14 2
15 4
16 8
17 */
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 387
Variadic Alias Template: My_tuple
1 #include <iostream>
2 #include <string>
3 #include <tuple>
4
5 template <class... Ts>
6 using My_tuple = std::tuple<bool, Ts...>;
7
8 int main() {
9 My_tuple<int, std::string> t(true, 42,
10 "meaning of life");
11 std::cout << std::get<0>(t) << ’ ’
12 << std::get<1>(t) << ’ ’
13 << std::get<2>(t) << ’\n’;
14 }
15
16 /* Output:
17 1 42 meaning of life
18 */
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 388
Fold Expressions
■ may want to apply binary operator (such as +) across all elements in
parameter pack
■ fold expression reduces (i.e., folds) parameter pack over binary operator
■ op: binary operator
■ E : expression that contains unexpanded parameter pack
■ I : expression that does not contain unexpanded parameter pack
Fold Syntax Expansion
unary left (... op E) ((E1 op E2 ) op ...) op EN
unary right (E op . . . ) E1 op (... op (EN−1 op EN ))
binary left (I op ... op E) (((I op E1 ) op E2 ) op ...) op EN
binary right (E op ... op I) E1 op (... op (EN−1 op (EN op I)))
[C++17 §17.5.3/9]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ unary fold of empty parameter pack: ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §17.5.3/9]
Operator Value for Empty Parameter Pack
&& true
|| false
, void()
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 389
Sum Example Without Fold Expression
1 #include <iostream>
2 #include <string>
3
4 using namespace std::string_literals;
5
6 template <class T>
7 auto sum(T x) {
8 return x;
9 }
10
11 template <class T, class... Args>
12 auto sum(T x, Args... args) {
13 return x + sum(args...);
14 }
15
16 int main() {
17 auto x = sum(42.5, -1.0, 0.5f);
18 auto y = sum("The "s, "answer "s, "is "s);
19 std::cout << y << x << ".\n";
20 // sum(); // ERROR: no matching function call
21 }
22
23 /* Output:
24 The answer is 42.
25 */
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 390
Sum Example With Fold Expression
1 #include <iostream>
2 #include <string>
3
4 using namespace std::string_literals;
5
6 template <class T, class... Args>
7 auto sum(T x, Args... args) {
8 return x + (... + args);
9 }
10
11 int main() {
12 auto x = sum(42.5, -1.0, 0.5f);
13 auto y = sum("The "s, "answer "s, "is "s);
14 std::cout << y << x << ".\n";
15 // sum(); // ERROR: no matching function call
16 }
17
18 /* Output:
19 The answer is 42.
20 */
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 391
Print Example Without Fold Expression
1 #include <iostream>
2 #include <string>
3
4 using namespace std::string_literals;
5
6 std::ostream& print() {return std::cout;}
7
8 template <class T>
9 std::ostream& print(const T& value) {
10 return std::cout << value;
11 }
12
13 template <class T, class... Args>
14 std::ostream& print(const T& value, const Args&... args) {
15 if (!(std::cout << value)) {
16 return std::cout;
17 }
18 return print(args...);
19 }
20
21 int main() {
22 print("The "s, "answer "s, "is "s, 42, ".\n"s);
23 print(); // OK: no-op
24 }
25
26 /* Output:
27 The answer is 42.
28 */
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 392
Print Example With Fold Expression
1 #include <iostream>
2 #include <string>
3
4 using namespace std::string_literals;
5
6 template <class... Args>
7 std::ostream& print(const Args&... args) {
8 return (std::cout << ... << args);
9 }
10
11 int main() {
12 print("The "s, "answer "s, "is "s, 42, ".\n"s);
13 print(); // OK: no-op
14 }
15
16 /* Output:
17 The answer is 42.
18 */
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 393
Fold Expression Example: All/Any/One/Even
1 #include <cassert>
2
3 template <class... Args>
4 bool all(Args... args)
5 {return (... && args);}
6
7 template <class... Args>
8 bool any(Args... args)
9 {return (... || args);}
10
11 template <class... Args>
12 bool one(Args... args)
13 {return (0 + ... + args) == 1;}
14
15 template <class... Args>
16 bool even(Args... args)
17 {return (1 + ... + args) % 2;}
18
19 int main() {
20 assert(all(false, true, true) == false);
21 assert(all(true, true, true) == true);
22 assert(any(false, false, true) == true);
23 assert(any(false, false, false) == false);
24 assert(one(true, false, false) == true);
25 assert(one(true, true, false) == false);
26 assert(even(true, true, false) == true);
27 assert(even(true, false, false) == false);
28 assert(even() == true && one() == false);
29 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 394
Constexpr-Friendly Heterogeneous List Example
1 #include <iostream>
2 #include <tuple>
3
4 // heterogeneous list of constant values
5 template <auto... vs> class value_list {
6 public:
7 constexpr value_list() : v_(vs...) {}
8 template <int n> constexpr auto get() const
9 {return std::get<n>(v_);}
10 constexpr int size() const {return sizeof...(vs);}
11 private:
12 std::tuple<decltype(vs)...> v_;
13 };
14
15 int main() {
16 constexpr value_list<42, true, ’A’> v;
17 constexpr auto n = v.size();
18 constexpr auto a = v.get<0>();
19 constexpr auto b = v.get<1>();
20 constexpr auto c = v.get<2>();
21 std::cout << n << ’ ’ << a << ’ ’ << b << ’ ’ << c << ’\n’;
22 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 395
Constexpr-Friendly Homogeneous List Example
1 #include <iostream>
2 #include <tuple>
3
4 // homogeneous list of constant values
5 template <auto v1, decltype(v1)... vs> class value_list {
6 public:
7 constexpr value_list() : v_(v1, vs...) {}
8 template <int n> constexpr auto get() const
9 {return std::get<n>(v_);}
10 constexpr int size() const {return 1 + sizeof...(vs);}
11 private:
12 std::tuple<decltype(v1), decltype(vs)...> v_;
13 };
14
15 int main() {
16 constexpr value_list<1, 2, 3> v;
17 constexpr auto n = v.size();
18 constexpr auto a = v.get<0>();
19 constexpr auto b = v.get<1>();
20 constexpr auto c = v.get<2>();
21 std::cout << n << ’ ’ << a << ’ ’ << b << ’ ’ << c << ’\n’;
22 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 396
Section 2.5.6
Template Specialization
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 397
Template Specialization
■ sometimes can be desirable to provide customized version of template for
certain choices of template parameters
■ customized version of templates can be specified through language
feature known as template specialization
■ two kinds of specialization: explicit and partial
■ explicit specialization (less formally known as full specialization):
customized version of template where all template parameters are fixed
■ partial specialization: customized version of template where only some
of template parameters are fixed
■ class templates, function templates, and variable templates can all be
specialized
■ alias templates cannot be specialized
■ class templates and variable templates can be partially or explicitly
specialized
■ function templates can only be explicitly specialized (not partially)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 398
Explicit Specialization
■ syntax for explicit specialization:
template <> declaration
■ declaration: declaration of templated entity (e.g., function, class, variable)
■ example:
// unspecialized template
template <class T, class U>
void func(T x, U y) { /* ... */ }
// explicit specialization of template
// (for when template parameters are bool, bool)
template <>
void func<bool, bool>(bool x, bool y) { /* ... */ }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 399
Partial Specialization
■ syntax for partial specialization of class template:
template <parameter_list> class_key
class_name <argument_list> declaration
■ syntax for partial specialization of variable template:
template <parameter_list> type_name
variable_name <argument_list> declaration
■ class_key: class or struct keyword (for class template)
■ class_name: class being specialized (for class template)
■ type_name: type of variable (for variable template)
■ variable_name: variable being specialized (for variable template)
■ argument_list: template argument list
■ declaration: declaration of templated entity (e.g., class, variable)
■ example:
// unspecialized template
template <class T, int N> class Widget { /* ... */ };
// partial specialization of template
// (for when first template parameter is bool)
template <int N> class Widget<bool, N> { /* ... */ };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 400
Explicitly-Specialized Function Template: printPointee
1 #include <iostream>
2
3 // unspecialized version
4 template <class T>
5 typename std::ostream& printPointee(
6 typename std::ostream& out, const T* p)
7 {return out << *p << ’\n’;}
8
9 // specialization
10 template <>
11 typename std::ostream& printPointee<void>(
12 typename std::ostream& out, const void* p)
13 {return out << *static_cast<const char*>(p) << ’\n’;}
14
15 int main() {
16 int i = 42;
17 const int* ip = &i;
18 char c = ’A’;
19 const void* vp = &c;
20 printPointee(std::cout, ip);
21 printPointee(std::cout, vp);
22 }
23
24 /* Output:
25 42
26 A
27 */
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 401
Explicitly-Specialized Class Template: is_void
1 template <class T>
2 struct is_void
3 {static constexpr bool value = false;};
4
5 template <>
6 struct is_void<void>
7 {static constexpr bool value = true;};
8
9 template <>
10 struct is_void<const void>
11 {static constexpr bool value = true;};
12
13 template <>
14 struct is_void<volatile void>
15 {static constexpr bool value = true;};
16
17 template <>
18 struct is_void<const volatile void>
19 {static constexpr bool value = true;};
20
21 static_assert(is_void<int>::value == false);
22 static_assert(is_void<double*>::value == false);
23 static_assert(is_void<void>::value == true);
24 static_assert(is_void<const void>::value == true);
25 static_assert(is_void<volatile void>::value == true);
26 static_assert(is_void<const volatile void>::value == true);
27
28 int main() {}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 402
Partially-Specialized Class Template
1 #include <iostream>
2
3 // unspecialized version
4 template <typename T, typename V>
5 struct Widget {
6 Widget() {std::cout << "unspecialized\n";}
7 };
8
9 // partial specialization
10 template <typename T>
11 struct Widget<int, T> {
12 Widget() {std::cout << "partial\n";}
13 };
14
15 // explicit specialization
16 template <>
17 struct Widget<int, int> {
18 Widget() {std::cout << "explicit\n";}
19 };
20
21 int main() {
22 Widget<double, int> w1; // unspecialized version
23 Widget<int, double> w2; // partial specialization
24 Widget<int, int> w3; // explicit specialization
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 403
Partially-Specialized Class Template: std::vector
■ std::vector class employs specialization
■ consider vector of elements of type T
■ most natural way to store elements is as array of T
■ if T is bool, such an approach makes very inefficient use of memory,
since each bool object requires one byte of storage
■ if T is bool, would be much more memory-efficient to use array of, say,
unsigned char and pack multiple bool objects in each byte
■ std::vector accomplishes this by providing (partial) specialization for
case that T is bool
■ declaration of base template for std::vector and its partial
specialization for case when T is bool are as follows:
template <class T, class Alloc = allocator<T>>
class vector; // unspecialized version
template <class Alloc>
class vector<bool, Alloc>; // partial specialization
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 404
Explicitly-Specialized Variable Template: is_void_v
1 template <class T>
2 constexpr bool is_void_v = false;
3
4 template <>
5 constexpr bool is_void_v<void> = true;
6
7 template <>
8 constexpr bool is_void_v<const void> = true;
9
10 template <>
11 constexpr bool is_void_v<volatile void> = true;
12
13 template <>
14 constexpr bool is_void_v<const volatile void> = true;
15
16 static_assert(is_void_v<int> == false);
17 static_assert(is_void_v<double*> == false);
18 static_assert(is_void_v<void> == true);
19 static_assert(is_void_v<const void> == true);
20 static_assert(is_void_v<volatile void> == true);
21 static_assert(is_void_v<const volatile void> == true);
22
23 int main() {}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 405
Explicitly-Specialized Variable Template: factorial
1 template <unsigned long long N>
2 constexpr unsigned long long
3 factorial = N * factorial<N - 1>;
4
5 template <>
6 constexpr unsigned long long
7 factorial<0> = 1;
8
9 int main() {
10 static_assert(factorial<5> == 120,
11 "factorial<5> failed");
12 static_assert(factorial<12> == 479’001’600,
13 "factorial<12> failed");
14 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 406
Partially-Specialized Variable Template: quotient
1 #include <limits>
2
3 // unspecialized version
4 template <int X, int Y>
5 constexpr int quotient = X / Y;
6
7 // partial specialization (which prevents division by zero)
8 template <int X>
9 constexpr int quotient<X, 0> = (X < 0) ?
10 std::numeric_limits<int>::min() : std::numeric_limits<int>::max();
11
12 static_assert(quotient<4, 2> == 2);
13 static_assert(quotient<5, 3> == 1);
14 static_assert(quotient<4, 0> == std::numeric_limits<int>::max());
15 static_assert(quotient<-4, 0> == std::numeric_limits<int>::min());
16
17 int main() {}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 407
Section 2.5.7
Miscellany
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 408
Overload Resolution and Substitution Failure
■ when creating candidate set (of functions) for overload resolution, some or
all candidates of that set may be result of instantiated templates with
template arguments substituted for corresponding template parameters
■ process of substituting template arguments for corresponding template
parameters can lead to invalid code
■ if certain types of invalid code result from substitution in any of following,
substitution failure said to occur:
2 all types used in function type (i.e., return type and types of all parameters)
2 all types used in template parameter declarations
2 all expressions used in function type
2 all expressions used in template parameter declaration
■ substitution failure not treated as error
■ instead, substitution failure simply causes overload to be removed from
candidate set
■ this behavior often referred to by term “substitution failure is not an error
(SFINAE)”
■ SFINAE behavior often exploited in template metaprogramming
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 409
Some Kinds of Substitution Failures
■ attempting to instantiate pack expansion containing multiple parameter
packs of differing lengths
■ attempting to create array with element type that is void, function type,
reference type, or abstract class type
■ attempting to create array with size that is zero or negative
■ attempting to use type that is not class or enumeration type in qualified
name
■ attempting to use type in nested name specifier of qualified ID, when type
does not contain specified member, or
2 specified member is not type where type is required
2 specified member is not template where template is required
2 specified member is not non-type where non-type is required
■ attempting to create pointer to reference type
■ attempting to create reference to void
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 410
Some Kinds of Substitution Failures (Continued)
■ attempting to create pointer to member of T when T is not class type
■ attempting to give invalid type to non-type template parameter
■ attempting to perform invalid conversion in either template argument
expression, or expression used in function declaration
■ attempting to create function type in which parameter has type of void,
or in which return type is function type or array type
■ attempting to create function type in which parameter type or return type
is abstract class
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 411
SFINAE Example: Truncate
1 class Real {
2 public:
3 using rounded_type = long long;
4 rounded_type truncate() const {
5 rounded_type result;
6 // ...
7 return result;
8 }
9 // ...
10 };
11
12 // function 1
13 template <class T>
14 typename T::rounded_type truncate(const T& x) {return x.truncate();}
15 // NOTE: example would not compile if return type specified as auto
16
17 // function 2
18 int truncate(double x) {return x;}
19
20 int main() {
21 Real r;
22 float f = 3.14f;
23 auto rounded_r = truncate(r);
24 // calls function 1 (only trivial conversions)
25 auto rounded_f = truncate(f);
26 // function 2 requires nontrivial conversions
27 // function 1 would only require trivial conversions but
28 // substitution failure occurs
29 // calls function 2 (with conversions)
30 }
[see overload
. . . . . . . . . ..resolution]
...........
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 412
SFINAE Example: Truncate Revisited
1 class Real {
2 public:
3 using rounded_type = long long;
4 rounded_type truncate() const {
5 rounded_type result;
6 // ...
7 return result;
8 }
9 // ...
10 };
11
12 // function 1
13 template <class T, class = typename T::rounded_type>
14 auto truncate(const T& x) {return x.truncate();}
15
16 // function 2
17 int truncate(double x) {return x;}
18
19 int main() {
20 Real r;
21 float f = 3.14f;
22 auto rounded_r = truncate(r);
23 // calls function 1 (only trivial conversions)
24 auto rounded_f = truncate(f);
25 // function 2 requires nontrivial conversions
26 // function 1 would only require trivial conversions but
27 // substitution failure occurs
28 // calls function 2 (with conversions)
29 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 413
std::enable_if and std::enable_if_t
■ to make SFINAE more convenient to exploit, class template
std::enable_if and alias template std::enable_if_t are provided
■ declaration of class template enable_if:
template <bool B, class T = void>
struct enable_if;
■ if B is true, class has member type type defined as T; otherwise, class
has no type member
■ possible implementation of enable_if:
1 template <bool B, class T = void>
2 struct enable_if {};
3
4 template <class T>
5 struct enable_if<true, T> {
6 using type = T;
7 };
■ declaration of alias template enable_if_t:
template <bool B, class T = void>
using enable_if_t = typename enable_if<B, T>::type;
■ if enable_if_t is used with its first parameter as false, substitution
failure will result
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 414
SFINAE Example: Modulo
1 #include <type_traits>
2 #include <cassert>
3 #include <iostream>
4
5 // ISO-Pascal modulo operator for signed integral types
6 template <class T> inline
7 std::enable_if_t<std::is_integral_v<T> && std::is_signed_v<T>, T>
8 mod(T x, T y) {
9 assert(y > 0);
10 if (x < 0) {x += (((-x) / y) + 1) * y;}
11 return x % y;
12 }
13
14 // ISO-Pascal modulo operator for unsigned integral types
15 template <class T> inline
16 std::enable_if_t<std::is_integral_v<T> && std::is_unsigned_v<T>, T>
17 mod(T x, T y)
18 {return x % y;}
19
20 int main() {
21 auto si = mod(-4, 3); // uses signed version
22 auto ui = mod(5u, 3u); // uses unsigned version
23 auto slli = mod(-5ll, 3ll); // uses signed version
24 auto ulli = mod(4ull, 3ull); // uses unsigned version
25 // auto f = mod(3.0, 4.0);
26 // ERROR: no matching function call
27 std::cout << si << ’ ’ << ui << ’ ’ << slli << ’ ’ << ulli << ’\n’;
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 415
Detection Idiom Example
1 #include <iostream>
2 #include <experimental/type_traits>
3
4 class Widget {
5 public:
6 void foo() const {}
7 // ...
8 };
9
10 class Gadget {
11 public:
12 void foo() {}
13 // ...
14 };
15
16 // helper template for testing if class has member function called
17 // foo that can be invoked on const object with no arguments.
18 template <class T>
19 using has_usable_foo_t = decltype(std::declval<const T&>().foo());
20
21 int main() {
22 std::cout
23 << "Widget "
24 << std::experimental::is_detected_v<has_usable_foo_t, Widget>
25 << ’\n’
26 << "Gadget "
27 << std::experimental::is_detected_v<has_usable_foo_t, Gadget>
28 << ’\n’;
29 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 416
Section 2.5.8
References
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 417
References I
1 D. Vandevoorde and N. M. Josuttis. C++ Templates: The Complete Guide.
Addison Wesley, 2002.
2 P. Sommerlad. Variadic and variable templates. Overload, 126:14–17,
Apr. 2015. Available online at
http://accu.org/index.php/journals/2087.
3 A. Sutton. Introducing concepts. Overload, 129:4–8, Oct. 2015. Available
online at http://accu.org/index.php/journals/2157.
4 A. Sutton. Defining concepts. Overload, 131:4–8, Feb. 2016. Available
online at http://accu.org/index.php/journals/2198.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 418
Talks I
1 Peter Sommerlad. Variadic Templates in C++11/C++14: An Introduction.
CppCon, Bellevue, WA, USA, Sept. 21, 2015. Available online at
https://youtu.be/R1G3P5SRXCw.
2 Arthur O’Dwyer. Template Normal Programming. CppCon, Bellevue, WA,
USA, Sept. 19, 2016. Available online at
https://youtu.be/vwrXHznaYLA and
https://youtu.be/VIz6xBvwYd8. (This talk is split into two parts.)
3 Arthur O’Dwyer. A Soupcon of SFINAE. CppCon, Bellevue, WA, USA,
Sept. 27, 2017. Available online at https://youtu.be/ybaE9qlhHvw.
4 Marshall Clow. The Detection Idiom: A Better Way to SFINAE. C++Now,
Aspen, CO, USA, May 19, 2017. Available online at
https://youtu.be/U3jGdnRL3KI.
Notwithstanding the talk’s title, this talk is actually about the functionality in the
Library Fundamentals TS related to is_detected, detected_or,
is_detected_exact, and is_detected_convertible.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 419
Talks II
5 Walter E. Brown. Modern Template Metaprogramming: A Compendium,
Part I. CppCon, Bellevue, WA, USA, Sept. 9, 2014. Available online at
https://youtu.be/Am2is2QCvxY.
6 Walter E. Brown. Modern Template Metaprogramming: A Compendium,
Part II. CppCon, Bellevue, WA, USA, Sept. 9, 2014. Available online at
https://youtu.be/a0FliKwcwXE.
7 Stephan T. Lavavej. Class Template Argument Deduction for Everyone.
CppCon, Bellevue, WA, USA, Sept. 27, 2018. Available online at
https://youtu.be/-H-ut6j1BYU.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 420
Section 2.6
Concepts
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 421
Motivating Example: Library (Without Concepts)
1 #include <algorithm>
2 #include <iterator>
3
4 template<class Iter, class Comp = std::less<>>
5 void shell_sort(Iter begin, Iter end, Comp less = {}) {
6 for (auto gap = (end - begin) / 2; gap != 0; gap /= 2) {
7 for (auto i = begin + gap; i != end; ++i) {
8 for (auto j = i; j >= begin + gap && less(*j, j[-gap]);
9 j -= gap) {std::iter_swap(j - gap, j);}
10 }
11 }
12 }
■ implementation of shell_sort requires Iter to be random-access
iterator type (and Comp to be valid comparator type) in order for code to
compile and work correctly
■ unfortunately, compiler has no way to know of this requirement
■ if, for example, Iter is not random-access iterator type, compiler
instantiates template anyways and (potentially many) unhelpful compiler
errors result
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 422
Motivating Example: Application (Without Concepts)
1 #include <iostream>
2 #include <list>
3 #include "shell_sort_legacy.hpp"
4
5 int main() {
6 std::list<int> v{1, 7, 2, 6, 3, 5, 4, 0};
7 shell_sort(v.begin(), v.end(), std::greater<>());
8 for (auto&& i : v) {std::cout << i << ’\n’;}
9 }
■ shell_sort requires that its first two arguments have random-access
iterator type
■ declaration of shell_sort (in included header) does not specify that
random-access iterators required
■ one or more compiler errors result that relate to implementation details of
shell_sort (e.g., errors related to operator-, operator+, and
operator-=)
■ none of error messages indicate true source of problem (namely, iterator
arguments to shell_sort not of random-access iterator type)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 423
Motivating Example: Library (Concepts)
1 #include <algorithm>
2 #include <concepts>
3 #include <iterator>
4 #include <utility>
5
6 template<std::random_access_iterator Iter, class Comp = std::less<>>
7 requires std::indirect_binary_predicate<Comp, Iter, Iter>
8 void shell_sort(Iter begin, Iter end, Comp less = {}) {
9 for (auto gap = (end - begin) / 2; gap != 0; gap /= 2) {
10 for (auto i = begin + gap; i != end; ++i) {
11 for (auto j = i; j >= begin + gap && less(*j, j[-gap]);
12 j -= gap) {std::iter_swap(j - gap, j);}
13 }
14 }
15 }
■ declaration of shell_sort indicates requirements on template
parameters Iter and Comp
■ compiler aware of these constraints and can enforce them
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 424
Motivating Example: Application (Concepts)
1 #include <iostream>
2 #include <list>
3 #include "shell_sort_concepts.hpp"
4
5 int main() {
6 std::list<int> v{1, 7, 2, 6, 3, 5, 4, 0};
7 shell_sort(v.begin(), v.end(), std::greater<>());
8 for (auto&& i : v) {std::cout << i << ’\n’;}
9 }
■ declaration of shell_sort (in included header) now uses concepts to
specify that random-access iterators required
■ compiler aware of this requirement and able to enforce it
■ compile error messages much more helpful
■ typically error message obtained will indicate that shell_sort cannot be
called because iterator arguments do not satisfy random-access iterator
requirement (or, more precisely, deduced type for iterator arguments does
not satisfy std::random_access_iterator concept)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 425
Section 2.6.1
Basics of Concepts
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 426
Concepts
■ class templates, function templates, variable templates, and non-template
member functions of class templates can be associated with constraints
■ constraints specify requirements on template arguments, which can be
used to select appropriate function overloads and template specializations
■ named set of requirements called concept
■ concept is predicate evaluated at compile time that is part of interface of
template and constrains template
■ concepts intended to model semantic requirements
■ concepts (as currently specified in language) can only formally express
syntactic requirements, however
■ consequently, only syntactic requirements can be checked by compiler
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 427
Benefits of Concepts
■ being able to constrain templates has numerous benefits
■ since constraints specified as part of language, compiler aware of
constraints
■ function template overloads and class/variable template specializations
can be selected based on properties of types
■ template interfaces can be specified more precisely, leading to more
readable code
■ improved compiler error messages are possible, since compiler can
determine if template being used with invalid template arguments
■ concepts and associated functionality in language provide simpler ways to
do things that would otherwise require template metaprogramming
techniques
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 428
Defining Concepts
■ to declare named set of requirements on template arguments (i.e.,
concept), concept keyword is used
■ syntax for concept definition:
template<template-parameter-list>
concept concept-name = constraint-expression;
■ constraint-expression is expression of type bool
■ cannot declare concept without defining it
■ definition must appear at namespace scope
■ recursion not allowed in concept definitions
■ although concept is itself template, cannot constrain concept (e.g., with
requires clause after template parameter list)
■ example of concept definition:
template<class T>
concept integral = std::is_integral_v<T>;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 429
Using Concepts in Expressions
■ concept is essentially compile-time predicate
■ consequently, can use concept in places where bool value is required
■ since concept is always constant expression, can use concept in constant
expression
■ example:
template<class T>
concept integral = std::is_integral_v<T>;
constexpr bool b = integral<int>;
// OK: integral<int> is constant expression
static_assert(!integral<double>);
// OK: integral<double> is constant expression and
// has value false
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 430
Some Examples of Concepts
1 #include <type_traits>
2 #include <string>
3
4 template<class T>
5 concept integral = std::is_integral_v<T>;
6 static_assert(integral<int> && !integral<double>);
7
8 template<class T>
9 concept floating_point = std::is_floating_point_v<T>;
10 static_assert(floating_point<double> && !floating_point<int>);
11
12 template<class T>
13 concept destructible = std::is_nothrow_destructible_v<T>;
14 static_assert(destructible<std::string> && !destructible<void>);
15
16 template<class T, class U>
17 concept same_as_impl = std::is_same_v<T, U>;
18 template<class T, class U>
19 concept same_as = same_as_impl<T, U> && same_as_impl<U, T>;
20 static_assert(same_as<int, int> && !same_as<int, double>);
21
22 template<class T, class... Args>
23 concept constructible_from = destructible<T> &&
24 std::is_constructible_v<T, Args...>;
25 static_assert(constructible_from<std::string, const char*> &&
26 !constructible_from<std::string, void*>);
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 431
Constraining Deduced Type Associated With Auto
■ in most contexts where auto keyword can be used, this keyword can be
prefixed by concept name in order to constrain deduced type
■ if deduced type does not satisfy constraint, compiler error results
■ example:
int func1();
float func2();
std::integral auto x = func1();
// OK: deduced type is int, which
// satisfies constraint std::integral
std::integral auto y = func2();
// ERROR: deduced type is float, which
// violates constraint std::integral
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 432
Example: Constraining Auto
1 #include <concepts>
2 #include <iostream>
3 #include <vector>
4
5 std::integral auto i = 42;
6 // std::integral auto j = 0.5; // ERROR: constraint violated
7
8 std::integral auto func1() {return 42;}
9 // std::integral auto func2() {return 0.5;}
10 // ERROR: constraint violated
11
12 std::integral auto func3(std::integral auto x) {return x + 1;}
13 std::integral auto func4(std::integral auto x) {return x + 0.5;}
14 // WRONG: error upon instantiation
15
16 std::predicate<int> auto f1 = [](int x) {return x < 0;};
17 // std::invocable<int> auto f2 = [](){};
18 // ERROR: constraint violated
19
20 int main() {
21 // func3(1.0);
22 // ERROR: template parameter constraint violation
23 // func4(1); // ERROR: return-type constraint violation
24 std::vector<int> v{1, 2, 3};
25 for (std::integral auto i : v) {/* ... */}
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 433
Requires Clauses
■ can constrain template by including requires clause in template
declaration
■ requires clause allowed in following places:
1 in case of function, class, or variable template: after template parameter list
2 in case of function template or nontemplate function of class template: at
end of function declaration (i.e., trailing requires clause)
■ can have requires clause after template parameter list and at end of
function declaration
■ syntax:
requires expr;
■ expr must be compile-time constant that evaluates to type bool
■ example:
template<class T> requires std::floating_point<T>
T foo(T x) {return x + T(0.5);}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 434
Requires Clauses (Continued)
■ expr must be:
2 primary expression (e.g., concept name, type trait, or any parenthesized
expression)
2 sequence of primary expressions joined by conjunction (&&) and/or
disjunction (||)
■ requirements deemed to be met if expr is:
1 well formed (i.e., no substitution failures); and
2 evaluates to true
■ evaluation of expression in requires clause (which is performed at compile
time) uses short-circuit logic
■ requires clause can also be used on nontemplate member function of
class template
■ (nontemplate) member function with requires clause will only be included
in class if requirement satisfied
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 435
Abbreviated Syntax for Constraining Templates
■ type template parameters can also be constrained by replacing
typename/class keyword with concept name
■ following two declarations are functionally equivalent (but not equivalent):
2 template<concept-name<args...> T>
2 template<class T> requires concept-name<T, args...>
■ for example, following declarations are functionally equivalent (but not
equivalent):
2 template<class T> requires std::integral<T>
void func(T x){/*... */}
2 template<std::integral T> void func(T x){/ ...
* */}
■ non-type template parameters can only be constrained using requires
clause
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 436
Combining Constraints in Template Declarations
■ as seen earlier, constraints can specified at several places in template
declaration:
1 type constraint in template parameter list (i.e., replacing typename/class
keyword in template parameter list with concept name)
2 requires clause immediately following template parameter list
3 in case of function template, type constraint in function parameter list (i.e.,
using auto with type constraint)
4 in case of function template, using trailing requires clause in function
declaration
■ if more than one of these mechanisms used, constraints are combined by
logical AND in order listed above
■ example:
template<widgety T> requires gadgety<T>
void func(T x, fooable auto y) requires barable<T>;
/* constraint is
widgety<T> && gadgety<T> && fooable<U> && barable<T>,
where U is deduced type for y */
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 437
Constrained Template Declarations
■ any two declarations of same template must specify constraints using
same form
■ example:
template<class T> requires std::integral<T>
void func(T);
template<std::integral T> void func(T);
// ILLEGAL: no diagnostic required
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 438
Example: Constrained Function Template
1 #include <concepts>
2 #include <iostream>
3
4 constexpr bool is_power_of_two(std::size_t n) {
5 int count = 0;
6 for (; n; n >>= 1) {
7 if (n & 1) {++count;}
8 }
9 return count == 1;
10 }
11
12 template<class T, std::size_t N>
13 requires std::floating_point<T> && (is_power_of_two(N))
14 void func(T (&a)[N]) {
15 std::cout << N << ’ ’ << sizeof(T) << ’\n’;
16 // ... (algorithm that requires power-of-two array size)
17 }
18
19 int main() {
20 float a[1024];
21 func(a); // OK: array size is power of 2
22 float b[1023];
23 // func(b); // ERROR: array size is not power of 2
24 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 439
Example: Constrained Class Template
1 #include <array>
2 #include <concepts>
3 #include <cstddef>
4
5 constexpr bool is_divisible(std::size_t m, std::size_t n)
6 {return !(m % n);}
7
8 template<std::integral T, std::size_t N>
9 requires (is_divisible(N, 4))
10 class Widget {
11 public:
12 // ...
13 private:
14 std::array<T, N> data_;
15 };
16
17 int main() {
18 Widget<int, 16> a; // OK: 16 is multiple of 4
19 // Widget<int, 15> b; // ERROR: 15 not multiple of 4
20 // Widget<float, 16> c; // ERROR: float not integral type
21 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 440
Example: Constrained Variable Template
1 #include <cassert>
2 #include <string>
3 #include <type_traits>
4 #include <vector>
5
6 template<class T>
7 concept nonbool_arithmetic = std::is_arithmetic_v<T> &&
8 !std::is_same_v<std::remove_cv_t<T>, bool>;
9
10 template<nonbool_arithmetic T>
11 const T forty_two{42};
12
13 int main() {
14 auto c = forty_two<char>; assert(c == 42);
15 auto i = forty_two<int>; assert(i == 42);
16 auto d = forty_two<double>; assert(d == 42.0);
17 // auto b = forty_two<bool>;
18 // ERROR: template constraint violated
19 // NOTE: narrowing conversion error if template constraint removed
20 // auto vi = forty_two<std::vector<int>>;
21 // ERROR: template constraint violated
22 // NOTE: would compile if template constraint removed
23 // auto s = forty_two<std::string>;
24 // ERROR: template constraint violated
25 // NOTE: would compile if template constraint removed
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 441
Example: Constrained Nontemplate Member Functions
1 #include <iostream>
2
3 template<bool enable_foo>
4 class Widget {
5 public:
6 void foo() requires (enable_foo) {std::cout << "foo\n";}
7 void bar() {std::cout << "bar\n";}
8 // ...
9 };
10
11 int main() {
12 Widget<true> a;
13 a.foo(); // OK: has foo member
14 a.bar();
15 Widget<false> b;
16 // b.foo(); // ERROR: no foo member
17 b.bar();
18 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 442
Example: Constrained Nontemplate Member Functions
1 #include <string>
2
3 template<bool I, bool S>
4 class widget {
5 public:
6 constexpr int foo(int x)
7 requires I {return x;}
8 constexpr std::string foo(const std::string& x)
9 requires S {return x;}
10 };
11
12 int main() {
13 using namespace std::string_literals;
14 widget<1, 1> w3;
15 // has foo(int) and foo(const std::string&)
16 w3.foo(42); // OK
17 w3.foo("hello"s); // OK
18 widget<0, 1> w2;
19 // has foo(const std::string&) but not foo(int)
20 w2.foo("hello"s); // OK
21 // w2.foo(42); // ERROR: no matching function call
22 widget<0, 0> w0;
23 // has neither foo(int) nor foo(const std::string&)
24 // w0.foo(42); // ERROR: no matching function call
25 // w0.foo("hello"s); // ERROR: no matching function call
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 443
Example: Constrained Generic Lambda
1 #include <concepts>
2 #include <iostream>
3
4 constexpr bool is_power_of_two(std::size_t n) {
5 for (; n; n >>= 1) {
6 if (n & 1) {return !(n >> 1);}
7 }
8 return false;
9 }
10
11 template<std::size_t N> concept power_of_two =
12 is_power_of_two(N);
13
14 // constrained generic lambda
15 auto f = []<std::floating_point T, std::size_t N>(T (&a)[N])
16 requires power_of_two<N> {
17 std::cout << N << ’\n’;
18 };
19
20 int main() {
21 float x[4] = {1, 2, 3, 4};
22 float y[3] = {1, 2, 3};
23 f(x); // OK: array size is power of 2
24 // f(y); // ERROR: no matching function call
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 444
Negating Requires-Clause Expression Not Opposite
1 #include <concepts>
2 #include <type_traits>
3
4 struct widget {using type = int;};
5 struct gadget {using type = float;};
6 struct doodad {};
7
8 template<class T>
9 constexpr bool func1(T x) {return false;}
10 template<class T>
11 requires std::integral<typename T::type>
12 constexpr bool func1(T x) {return true;}
13
14 // func2 is not functionally equivalent to func1
15 template<class T>
16 constexpr bool func2(T x) {return true;}
17 template<class T>
18 requires (!std::integral<typename T::type>)
19 constexpr bool func2(T x) {return false;}
20
21 static_assert(func1(widget{}) == func2(widget{}));
22 static_assert(func1(gadget{}) == func2(gadget{}));
23 static_assert(func1(doodad{}) != func2(doodad{})); // why not equal?
24 static_assert(func1(42) != func2(42)); // why not equal?
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 445
Requires Expressions
■ requires expression provides mechanism for testing requirements for
types
■ syntax for requires expression:
requires parameter-list(optional) requirement-sequence
■ requires expression has type bool
■ evaluates to true if constraints satisfied and false otherwise
■ requires expression always constant expression
■ expressions appearing in requirement sequence not evaluated
■ optional parameter list used to obtain dummy variables of specific types
that can be used in body of requires expression
■ expressions in body of requires expression may be ill formed but
parameter list being ill formed is compile error
■ example:
template<class T> concept b = requires(T x) {++x;};
static_assert(b<int> && !b<bool>);
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 446
Requires Expressions (Continued)
■ if used outside template, requires expression evaluating to false yields
compiler error
■ four types of requirements are supported:
1 simple
2 type
3 compound
4 nested
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 447
Simple Requirements
■ simple requirement asserts expression is valid
■ expression is not evaluated
■ example:
// C<T> is true if a + b is valid expression
template<class T> concept C = requires(T a, T b) {
a + b
};
// incrementable<T> is true if T can be both
// pre- and post-incremented
template<class T> concept incrementable = requires(T a) {
++a;
a++;
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 448
Type Requirements
■ type requirement asserts validity of type
■ example:
// C<T> is true if T has type members iterator
// and const_iterator
template<class T> concept C = requires {
typename T::iterator;
typename T::const_iterator;
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 449
Compound Requirements
■ compound requirement asserts properties of expression
■ in addition to asserting that expression is valid, compound requirement
can assert:
2 expression is nonthrowing
2 type of expression satisfies specified concept
■ if noexcept specified, expression must not be potentially throwing
■ if requirement on type of expression specified, asserts type of expression
satisfies given concept
■ in particular, “{E} -> Concept<Args...>;” equivalent to
“E; requires Concept<decltype((E)), Args...>;”
■ example:
/* c<T> is true if x.foo(i) is valid expression
of type int and does not throw, where x has type T
and i has type int */
template<class T> concept c = requires(T x, int i) {
{x.foo(i)} noexcept -> std::same_as<int>;
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 450
Nested Requirements
■ nested requirement asserts constraint expression is true
■ constraint expression is simply constant expression of type bool
■ example:
/* c<T> is true if T does not require more
storage than void pointer */
template<class T> concept c = requires {
requires sizeof(T) <= sizeof(void*);
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 451
Example: Requires Expression in Concept Definition
1 #include <concepts>
2 #include <iostream>
3
4 template<class T>
5 concept stream_insertable = requires(T x, std::ostream& out) {
6 {out << x} -> std::same_as<std::ostream&>;
7 };
8
9 template<stream_insertable... Ts>
10 std::ostream& print(std::ostream& out, Ts... args) {
11 return (out << ... << args);
12 }
13
14 struct Widget {};
15
16 int main() {
17 print(std::cout, "hello", ’ ’, 42, ’ ’, 42.42f, ’\n’);
18 //print(std::cout, Widget{});
19 // ERROR: template constraint violation (stream_insertable)
20 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 452
Example: Requires Expression in Requires Clause
1 #include <concepts>
2
3 struct widget {
4 int foo() {return 42;}
5 };
6
7 template<class T>
8 requires requires(T x) {
9 {x.foo()} -> std::same_as<int>;
10 }
11 int func(T x) {return x.foo() + 1;}
12
13 int main() {
14 widget w;
15 func(w);
16 // func(42); // ERROR: constraint violation
17 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 453
Example: Bug in Requires Expression
1 #include <array>
2
3 template<class T>
4 concept int_sized = requires {
5 sizeof(T) == sizeof(int); // BUG: missing requires keyword
6 };
7
8 static_assert(int_sized<char> && sizeof(char) == 1);
9 // OK: YIKES!
10 static_assert(int_sized<char[4096]> &&
11 sizeof(char[4096]) == 4096);
12 // OK: YIKES!
13 static_assert(int_sized<std::array<int, 4096>> &&
14 sizeof(std::array<int, 4096>) == 4096 * sizeof(int));
15 // OK: YIKES!
16
17 static_assert(!int_sized<void>);
18 // OK: YIKES! void has no size
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 454
Example: Selecting Best Swap Algorithm [smart_swap]
1 #include <concepts>
2 #include <utility>
3
4 template<std::movable T>
5 void smart_swap(T& x, T& y) {
6 constexpr bool has_member_swap = requires(T x, T y) {
7 x.swap(y);
8 };
9 constexpr bool has_nonmember_swap = requires(T x, T y) {
10 swap(x, y);
11 };
12 if constexpr(has_member_swap) {
13 // use member swap
14 x.swap(y);
15 } else if constexpr(has_nonmember_swap) {
16 // use nonmember swap
17 swap(x, y);
18 } else {
19 // use general swap algorithm
20 T tmp = std::move(x);
21 x = std::move(y);
22 y = std::move(tmp);
23 }
24 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 455
Example: Selecting Best Swap Algorithm [Application]
1 #include "smart_swap.hpp"
2 #include <iostream>
3
4 namespace foo {
5 struct widget {
6 void swap(widget& other) {std::cout << "widget member swap\n";}
7 };
8 void swap(widget& x, widget& y)
9 {std::cout << "widget nonmember swap\n";}
10 struct gadget {};
11 void swap(gadget& x, gadget& y)
12 {std::cout << "gadget nonmember swap\n";}
13 struct doodad {};
14 }
15
16 int main() {
17 foo::widget w1, w2;
18 foo::gadget g1, g2;
19 foo::doodad d1, d2;
20 smart_swap(w1, w2); // uses member swap
21 smart_swap(g1, g2); // uses nonmember swap
22 smart_swap(d1, d2); // uses general swap algorithm
23 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 456
Accumulate Example
1 #include <array>
2 #include <cassert>
3 #include <concepts>
4 #include <iterator>
5 #include <list>
6 #include <sstream>
7
8 template<std::input_iterator I, class T>
9 requires requires(I i, T t) {
10 {t += *i} -> std::same_as<T&>;
11 }
12 constexpr T accumulate(I first, I last, T init) {
13 for (; first != last; ++first) {init += *first;}
14 return init;
15 }
16
17 int main() {
18 constexpr std::array<int, 4> a{1, 2, 3};
19 static_assert(accumulate(a.begin(), a.end(), 0) == 6);
20 const std::list<int> b{1, 2, 3};
21 assert(accumulate(b.begin(), b.end(), 0) == 6);
22 std::stringstream ss("1.0 0.5 0.5");
23 assert(accumulate(std::istream_iterator<float>(ss),
24 std::istream_iterator<float>(), 0.0f) == 2.0);
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 457
Overload Resolution and Template Constraints
■ when considering template functions during overload resolution, most
constrained template always preferred from those that are viable (i.e.,
constraints satisfied)
■ if explicit specialization available, chosen over all other template overloads
■ constrained template (whose constraints are satisfied) chosen over
unconstrained template
■ when more than one constrained template is viable (i.e., its constraints
satisfied), overload resolution prefers most constrained one
■ how “most constrained” is defined is somewhat complicated
■ to begin, we only consider some simple common cases
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 458
Some Simple Cases for Constraint Ordering
■ suppose that c1 , c2 , . . . , cn are constraint expressions that each consist
only of single concept name
■ c1 && c2 && . . . && cn more constrained than c1 && c2 && . . . && cn−1
■ c1 || c2 || . . . || cn−1 more constrained than c1 || c2 || . . . || cn
■ note that if any of c1 , c2 , . . . , cn include more than only single concept
name (e.g., negation operator or more complicated boolean expression)
issue of constraint ordering becomes significantly more involved
■ example:
template<class T> concept widgety = /* ... */;
template<class T> concept gadgety = /* ... */;
template<class T> requires widgety<T>
void func1(T) {} // A
template<class T> requires widgety<T> && gadgety<T>
void func1(T) {} // B
// B more constrained than A
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 459
Simple Overload Resolution Example
1 #include <cassert>
2 #include <concepts>
3
4 // unconstrained primary template
5 template<class T> int func(T x) {return 0;}
6
7 // constrained template
8 template<class T> requires std::integral<T>
9 int func(T x) {return 1;}
10
11 // more specialized template
12 template<class T> int func(T* x) {return 2;}
13
14 // explicit template specialization
15 template<> int func<char>(char x) {return 3;}
16
17 // nontemplate
18 int func(int x) {return 4;}
19
20 int main() {
21 assert(func(0.0) == 0);
22 assert(func(42L) == 1);
23 int* ip = nullptr; assert(func(ip) == 2);
24 assert(func(’*’) == 3);
25 assert(func(42) == 4);
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 460
Overloading Example: Constraint Equivalence
1 #include <type_traits>
2
3 template<class T>
4 requires std::is_integral_v<T>
5 constexpr int func1(T x) {return 0;}
6
7 template<class T>
8 requires std::is_integral_v<T> && std::is_unsigned_v<T>
9 constexpr int func1(T x) {return 1;}
10
11 template<class T> concept integral = std::is_integral_v<T>;
12 template<class T> concept notsigned = std::is_unsigned_v<T>;
13
14 template<class T>
15 requires integral<T>
16 constexpr int func2(T x) {return 0;}
17
18 template<class T>
19 requires integral<T> && notsigned<T>
20 constexpr int func2(T x) {return 1;}
21
22 static_assert(func1(42) == 0);
23 // static_assert(func1(42U) == 1); // ERROR: ambiguous function call
24 static_assert(func2(42) == 0);
25 static_assert(func2(42U) == 1);
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 461
Specialization and Class/Variable Template Constraints
■ when class/variable template instantiated, most constrained template
selected from those that are viable (i.e., constraints satisfied)
■ if explicit specialization available, chosen over all others
■ constrained template chosen over unconstrained template
■ when more than one constrained template viable, most constrained one
chosen
■ how “most constrained” is defined is somewhat complicated
■ to begin, we only consider some simple common cases
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 462
Simple Class-Template Specialization Example
1 #include <cassert>
2 #include <concepts>
3 #include <string>
4
5 // unconstrained primary template
6 template<class T> struct widget {
7 static std::string value() {return "primary";}
8 };
9
10 // partial specialization
11 template<std::integral T> struct widget<T> {
12 static std::string value() {return "integral";}
13 };
14
15 // explicit specialization
16 template<> struct widget<bool> {
17 static std::string value() {return "explicit";}
18 };
19
20 int main() {
21 assert(widget<double>::value() == "primary");
22 assert(widget<int>::value() == "integral");
23 assert(widget<bool>::value() == "explicit");
24 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 463
Constrained Variable Templates and Specialization
1 #include <cassert>
2 #include <complex>
3 #include <optional>
4 #include <string>
5 #include <type_traits>
6
7 template<class T>
8 requires std::destructible<T>
9 const auto forty_two = std::optional<T>();
10
11 template<class T>
12 requires std::destructible<T> &&
13 std::constructible_from<std::optional<T>, std::in_place_t, int>
14 const auto forty_two<T> = std::optional<T>(std::in_place_t{}, T(42));
15
16 int main() {
17 auto s = forty_two<std::string>;
18 assert(!s.has_value());
19 auto cf = forty_two<std::complex<float>>;
20 assert(cf.has_value() && *cf == 42.0f);
21 auto i = forty_two<int>;
22 assert(i.has_value() && *i == 42);
23 auto f = forty_two<float>;
24 assert(f.has_value() && *f == 42.0f);
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 464
Section 2.6.2
Constraint Ordering
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 465
Prelude to Constraint Ordering
■ in order to understand subsequent material on constraint ordering, one
should be familiar with:
2 basic
. . . . . . .operations
. . . . . . . . . . . . .in boolean algebra
. .....................
2 laws
. . . . . .of
. . .boolean
. . . . . . . . . . algebra
. . .......
2 subsumption
. . . . . . . . . . . . . . . .relationships for boolean expressions
. . . . . ......................................
2 disjunctive
. . . . . . . . . . . . . normal form
. . . . . . . . ......
2 conjunctive
. . . . . . . . . . . . . .normal form
. . . . . . . .......
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 466
Ordering of Constraints
■ language often needs to choose between multiple constrained versions of
template
■ language defines set of rules for ordering constraint expressions from
most constrained to least constrained
■ ordering is partial, so some constraint expressions are incomparable
■ if two constraint expressions are comparable, constraint that comes first in
this ordering is deemed more constrained
■ constraint expressions treated as abstract mathematical formulas in
boolean algebra involving conjunction and disjunction (but not negation)
■ negation is not considered in abstract mathematical sense
■ can express abstract mathematical formulas in various normal (i.e.,
canonical) forms, such as disjunctive normal form (DNF) and conjunctive
normal form (CNF)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 467
Normal Form of Constraint
■ each constraint can be represented using abstract mathematical
representation known as normal form
■ normal form of A || B is disjunction of normal forms of A and B
■ normal form of A && B is conjunction of normal forms of A and B
■ normal form of concept C<A1, A2,...> is normal form of constraint
expression associated with C after substituting template parameters
■ normal form of expression E that does not contain conjunctions,
disjunctions, or concepts is simply E
■ two atomic constraints appearing in normal form only deemed equivalent
if correspond to same location (i.e., line/column) in source code
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 468
Normal Form Example (1)
■ suppose we have following definitions:
1 template<class T> concept scalar =
2 std::is_scalar_v<T>;
3 template<class T> concept integral =
4 scalar<T> && std::is_integral_v<T>;
5 template<class T> concept floating_point =
6 scalar<T> && std::is_floating_point_v<T>;
■ consider converting following constraint to normal form:
integral<T> || floating_point<T>
■ performing constraint normalization, we have:
1 scalar<T> && std::is_integral_v<T> ||
scalar<T> && std::is_floating_point_v<T>
2 std::is_scalar_v<T> && std::is_integral_v<T> ||
std::is_scalar_v<T> && std::is_floating_point_v<T> (DNF)
■ using distributive property, we obtain CNF:
2 std::is_scalar_v<T> && (std::is_integral_v<T> ||
std::is_floating_point_v<T>)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 469
Normal Form Example (2)
■ suppose we have following definitions:
1 template<class T> concept integral =
2 std::is_scalar_v<T> && std::is_integral_v<T>;
3 template<class T> concept floating_point =
4 std::is_scalar_v<T> && std::is_floating_point_v<T>;
■ consider converting following constraint to normal form:
integral<T> || floating_point<T>
■ performing constraint normalization, we have:
2 std::is_scalar_v<T> && std::is_integral_v<T> ||
std::is_scalar_v<T> && std::is_floating_point_v<T> (DNF)
■ using distributive property, we obtain CNF:
2 (std::is_scalar_v<T> || std::is_scalar_v<T>) &&
(std::is_scalar_v<T> || std::is_floating_point_v<T>) &&
(std::is_integral_v<T> || std::is_scalar_v<T>) &&
(std::is_integral_v<T> || std::is_floating_point_v<T>)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 470
Partial Ordering of Constraints
■ A constraint P subsumes a constraint Q if and only if, for every disjunctive
clause Pi in the disjunctive normal form of P, Pi subsumes every
conjunctive clause Q j in the conjunctive normal form of Q.
■ A disjunctive clause Pi subsumes a conjunctive clause Q j if and only if
there exists an atomic constraint Pi,a in Pi for which there exists an atomic
constraint Q j,b in Q j such that Pi,a subsumes Q j,b .
■ An atomic constraint A subsumes another atomic constraint B if and only
if A and B are identical.
■ A declaration D1 is at least as constrained as a declaration D2 if D1 and
D2 are both constrained declarations and D1 ’s associated constraints
subsume those of D2 ; or D2 has no associated constraints.
■ A declaration D1 is more constrained than another declaration D2 when
D1 is at least as constrained as D2 , and D2 is not at least as constrained
as D1 .
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 471
Overloading Example: Constraint Subsumption
1 template<bool B> concept a = B;
2 template<bool B> concept b = B;
3 template<bool B> concept c = B;
4
5 template<bool A, bool B, bool C> requires a<A> || b<B> && c<C>
6 constexpr int f() {return 0;}
7 // DNF is (a<A>) || (b<B> && c<C>); CNF is (a<A> || b<B>) && (a<A> || c<C>)
8
9 template<bool A, bool B, bool C> requires a<A> || b<B> || c<C>
10 constexpr int f() {return 1;}
11 // DNF is (a<A>) || (b<B>) || (c<C>); CNF is (a<A> || b<B> || c<C>)
12
13 // a<A> || subsumes a || b<B> || c<C>
14 // b<B> && c<C> subsumes a<A> || b<B> || c<C>
15 // a<T> || b<T> && c<T> subsumes a<A> || b<B> || c<C>
16 // a<A> subsumes a<A> || b<B>
17 // b<B> does not subsume a<A> || c<C>
18 // c<C> does not subsume a<A> || b<B>
19 // a<A> || b<B> || c<C> does not subsume a<A> || b<B> && c<C>
20
21 // static_assert(f<0, 0, 0>()); // ERROR: no matching function call
22 static_assert((f<0, 0, 1>() == 1)); static_assert((f<0, 1, 0>() == 1));
23 static_assert((f<0, 1, 1>() == 0)); static_assert((f<1, 0, 0>() == 0));
24 static_assert((f<1, 0, 1>() == 0)); static_assert((f<1, 1, 0>() == 0));
25 static_assert((f<1, 1, 1>() == 0));
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 472
Overloading Example: Negation
1 template<bool B> concept a = B;
2 template<bool B> concept not_a = !a<B>;
3 template<bool B> concept b = B;
4
5 template<bool A, bool B> requires (!a<A>)
6 constexpr int func1() {return 0;}
7 template<bool A, bool B> requires (!a<A>) && b<B>
8 constexpr int func1() {return 1;}
9 // (!a<A>) && b<B> does not subsume (!a<A>) and vice versa
10
11 template<bool A, bool B> requires not_a<A>
12 constexpr int func2() {return 0;}
13 template<bool A, bool B> requires not_a<A> && b<B>
14 constexpr int func2() {return 1;}
15 // not_a<A> && b<B> subsumes not_a<A>
16
17 int main() {
18 static_assert(func1<0, 0>() == 0);
19 // func1<0, 1>(); // ERROR: ambiguous function call
20 static_assert(func2<0, 0>() == 0);
21 static_assert(func2<0, 1>() == 1);
22 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 473
Overloading Example: Negation
1 template<bool B> concept a = B;
2 template<bool B> concept not_a = !a<B>;
3 template<bool B> concept b = B;
4
5 template<bool A, bool B> requires (!a<A>)
6 constexpr int func1() {return 0;}
7 template<bool A, bool B> requires (!a<A>) || b<B>
8 constexpr int func1() {return 1;}
9 // (!a<A>) does not subsume (!a<A>) || b<B> and vice versa
10
11 template<bool A, bool B> requires not_a<A>
12 constexpr int func2() {return 0;}
13 template<bool A, bool B> requires not_a<A> || b<B>
14 constexpr int func2() {return 1;}
15 // not_a<A> subsumes not_a<A> || b<B>
16
17 int main() {
18 // func1<0, 0>(); // ERROR: ambiguous function call
19 // func1<0, 1>(); // ERROR: ambiguous function call
20 static_assert(func1<1, 1>() == 1);
21 static_assert(func2<0, 0>() == 0);
22 static_assert(func2<0, 1>() == 0);
23 static_assert(func2<1, 1>() == 1);
24 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 474
Section 2.6.3
Additional Examples
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 475
Overloading Example: Advance
1 #include <cassert>
2 #include <iterator>
3 #include <list>
4 #include <sstream>
5 #include <vector>
6
7 template<std::input_iterator I>
8 void advance(I& iter, std::iter_difference_t<I> n) {while (n--) {++iter;}}
9
10 template<std::bidirectional_iterator I>
11 void advance(I& iter, std::iter_difference_t<I> n) {
12 if (n > 0) {while (n--) {++iter;}}
13 else {while (n++) {--iter;}}
14 }
15
16 template<std::random_access_iterator I>
17 void advance(I& iter, std::iter_difference_t<I> n) {iter += n;}
18
19 int main() {
20 std::vector<int> v{1, 2, 3, 4};
21 auto vi = v.begin();
22 advance(vi, 4);
23 assert(vi == v.end());
24 std::list<int> l{1, 2, 3, 4};
25 auto li = l.begin();
26 advance(li, 4);
27 assert(li == l.end());
28 std::stringstream s("1 2 3 4");
29 auto si = std::istream_iterator<int>(s);
30 advance(si, 4);
31 assert(si == std::istream_iterator<int>());
32 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 476
Example: Sum
1 #include <cassert>
2 #include <concepts>
3 #include <string>
4 #include <type_traits>
5 #include <utility>
6
7 template<class T, class... Ts>
8 inline constexpr bool are_same_v = std::conjunction_v<std::is_same<T, Ts>...>;
9
10 template<class T> concept accumulatable = std::copyable<T> &&
11 requires(T x, T y) {{x + y} -> std::same_as<T>;};
12
13 template<class T, class... Ts>
14 requires are_same_v<T, Ts...> && accumulatable<T>
15 constexpr T sum(T first, Ts... args) {
16 if constexpr(sizeof...(Ts) >= 2) {
17 return std::move(first) + (... + std::move(args));
18 } else {return first;}
19 }
20
21 int main() {
22 using namespace std::literals;
23 static_assert(sum(1, 1, 1) == 3);
24 {int i = 1; assert(sum(i, i, i) == 3);}
25 static_assert(sum(1.0, 1.0, 1.0) == 3.0);
26 // auto x = sum(1, 1.0); // ERROR: constraints not met
27 {double d = 1.0; assert(sum(d, d, d) == 3.0);}
28 static_assert(sum(1) == 1);
29 {std::string s("!"); assert(sum(s, s, s) == "!!!"s);}
30 assert(sum("Hello,"s, " "s, "World!"s) == "Hello, World!"s);
31 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 477
Example: Constrained Nontemplate Member Functions
1 #include <type_traits>
2 #include <string>
3
4 template<class T> concept trivially_copy_constructible =
5 std::is_trivially_copy_constructible_v<T>;
6 template<class T> concept trivially_move_constructible =
7 std::is_trivially_move_constructible_v<T>;
8 // ...
9
10 template<class T> class optional {
11 public:
12 constexpr optional() : has_value_(false) {}
13 constexpr optional(const optional& other) requires
14 std::copy_constructible<T> {/* ... */}
15 optional(const optional&) requires std::copy_constructible<T> &&
16 trivially_copy_constructible<T> = default;
17 constexpr optional(optional&& other)
18 noexcept(std::is_nothrow_move_constructible_v<T>) requires
19 std::move_constructible<T> {/* ... */}
20 constexpr optional(optional&&) requires std::move_constructible<T> &&
21 trivially_move_constructible<T> = default;
22 ~optional() {/* ... */}
23 ~optional() requires std::is_trivially_destructible_v<T> = default;
24 // ...
25 private:
26 bool has_value_;
27 // ...
28 };
29
30 static_assert(std::is_trivially_destructible_v<optional<int>>);
31 static_assert(std::is_trivially_copy_constructible_v<optional<int>>);
32 static_assert(!std::is_trivially_destructible_v<optional<std::string>>);
33 static_assert(!std::is_trivially_copy_constructible_v<optional<std::string>>);
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 478
Section 2.6.4
Standard-Library Concepts
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 479
Concepts Defined by Standard Library
■ standard library defines many concepts
■ although concepts (as currently specified in language) can only express
syntactic requirements, concepts intended to convey semantic
requirements
■ concepts in standard library typically have implied requirements beyond
syntactic ones (e.g., semantic or time-complexity requirements)
■ for example, some concepts imply certain operations on type should have
particular time complexity (e.g., constant time)
■ programmer solely responsible for ensuring non-syntactic requirements
are met, as compiler cannot check them
■ subsequent slides summarize concepts defined in standard library
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 480
Basic Concepts (1)
Core Language Concepts (in concepts Header)
Name Description
same_as type is same as another type
derived_from type is derived from another type
convertible_to type is implicitly convertible to another type
common_reference_with two types share common reference type
common_with two types share common type
integral type is integral type
signed_integral type is integral type that is signed
unsigned_integral type is integral type that is unsigned
floating_point type is floating-point type
assignable_from type is assignable from another type
swappable type can be swapped
swappable_with two types can be swapped with each other
destructible type can be destroyed
constructible_from variable of type can be constructed from or bound to set of argu-
ment types
default_initializable type can be default constructed
move_constructible type can be move constructed
copy_constructible type can be copy (and move) constructed
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 481
Basic Concepts (2)
Comparison Concepts (in concepts Header)
Name Description
equality_comparable operator== and operator!= for type consistent with
equality
equality_comparable_with operator== and operator!= between two types con-
sistent with equality
totally_ordered comparison operators for type consistent with strict total or-
der
totally_ordered_with comparison operators for type consistent with strict total or-
der
Object Concepts (in concepts Header)
Name Description
movable type can be moved and swapped
copyable type can be copied and is movable
semiregular type can be default constructed and is copyable
regular type is semiregular and equality_comparable
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 482
Basic Concepts (3)
Callable Concepts (in concepts Header)
Name Description
invocable∗ callable type can be invoked with given set of arguments using
std::invoke
regular_invocable∗ callable type that satisfies requirements of invocable, is equal-
ity preserving, and does not modify function object or arguments
predicate callable type is boolean predicate
relation callable type is binary relation
equivalence_relation callable type is relation that imposes equivalence relation
strict_weak_order callable type is relation that imposes strict weak ordering
∗ distinction between invocable and regular_invocable is purely semantic
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 483
Iterator-Related Concepts (1)
Iterator Concepts (in iterator Header)
Name Description
indirectly_readable type has dereferencing operator whose result can be read
indirectly_writable type has dereferencing operator whose result can be written
weakly_incrementable type is semiregular and can be incremented with pre- and post-
increment operators
incrementable type is regular, weakly_incrementable, and increment operation
is equality_comparable
input_or_output_iterator type is input or output iterator type
sentinel_for type is sentinel for given input or output iterator type
sized_sentinel_for type is sentinel for given input or output iterator type and provides
constant-time difference operator
input_iterator type is input iterator type
output_iterator type is output iterator type
forward_iterator type is forward iterator type
bidirectional_iterator type is bidirectional iterator type
random_access_iterator type is random-access iterator type
contiguous_iterator type is random-access iterator type that refers to elements stored
contiguously in memory
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 484
Iterator-Related Concepts (2)
Indirect Callable Concepts (in iterator Header)
Name Description
indirectly_unary_invocable type is callable and can be invoke with result of deref-
erencing indirectly_readable type
indirectly_regular_unary_invocable type is callable and can be invoke with result of deref-
erencing indirectly_readable type
indirect_unary_predicate type is predicate that can be invoked with
one argument resulting from dereferencing
indirectly_readable type
indirect_binary_predicate type is predicate that can be invoked with
two arguments resulting from dereferencing
indirectly_readable type
indirect_equivalence_relation type is equivalent_relation that can be invoked
with two arguments resulting from dereferencing
indirectly_readable type
indirect_strict_weak_order type is strict_weak_order that can be invoked
with two arguments resulting from dereferencing
indirectly_readable type
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 485
Iterator-Related Concepts (3)
Concepts for Common Algorithm Requirements (in iterator Header)
Name Description
indirectly_movable values can be moved from indirectly_readable type
to indirectly_writable type
indirectly_movable_storable type is indirectly_movable but move may be per-
formed via intermediate object
indirectly_copyable values can be copied from indirectly_readable type
to indirectly_writable type
indirectly_copyable_storable type is indirectly_copyable but copy may be per-
formed via intermediate object
indirectly_swappable values referenced by two indirectly_readable types
can be swapped
indirectly_comparable values referenced by two indirectly_readable types
can be compared
permutable type meets requirements of algorithm that reorders ele-
ments in place
mergeable type meets requirements of algorithm that merge sorted
sequences into output sequence by copying elements
sortable type meets requirements of algorithms that permute se-
quences into ordered sequences
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 486
Range-Related Concepts
Range Concepts (in Namespace std::ranges in ranges Header)
Name Description
range type is range (i.e., has ranges::begin and ranges::end op-
erations that return iterator and sentinel, respectively)
borrowed_range type is range whose iterators continue to be valid beyond end of
range’s lifetime
sized_range type is range that provides constant-time size operation
view type is range that is view (i.e., has constant-time copy/move/as-
signment operations)
input_range type is range whose iterator type satisfies input_iterator
output_range type is range whose iterator type satisfies output_iterator
forward_range type is range whose iterator type satisfies forward_iterator
bidirectional_range type is range whose iterator type satisfies
bidirectional_iterator
random_access_range type is range whose iterator type satisfies
random_access_iterator
contiguous_range type is range whose iterator type satisfies
contiguous_iterator
common_range type is range whose iterator and sentinel types are same
viewable_range type is range that can be safely converted to view
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 487
Types Satisfying/Violating Various Concepts (1)
1 #include <complex>
2 #include <concepts>
3 #include <exception>
4 #include <functional>
5 #include <mutex>
6 #include <thread>
7 #include <string>
8 #include <vector>
9
10 static_assert(
11 std::copyable<std::string> &&
12 !std::movable<std::mutex> &&
13 std::regular<std::string> &&
14 std::totally_ordered<std::string> &&
15 !std::totally_ordered<std::complex<float>> &&
16 std::predicate<std::less<int>, int, int> &&
17 std::same_as<int, decltype(42)> &&
18 std::floating_point<double> &&
19 std::derived_from<std::logic_error, std::exception> &&
20 std::integral<bool> &&
21 std::swappable<std::thread> &&
22 std::constructible_from<std::string, char*> &&
23 std::constructible_from<std::complex<double>, double> &&
24 std::equality_comparable<std::string::iterator> &&
25 std::equality_comparable_with<std::string::iterator,
26 std::string::const_iterator> &&
27 std::assignable_from<int&, int> &&
28 !std::assignable_from<int, int> &&
29 std::invocable<int(int, int), double, float>
30 );
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 488
Types Satisfying/Violating Various Concepts (2)
1 #include <ranges>
2 #include <vector>
3 #include <list>
4 #include <forward_list>
5 #include <string>
6
7 static_assert(
8 std::ranges::range<std::string> &&
9 std::ranges::range<std::vector<int>> &&
10 std::ranges::sized_range<std::list<int>> &&
11 !std::ranges::view<std::vector<int>> &&
12 std::ranges::forward_range<std::forward_list<int>> &&
13 std::ranges::bidirectional_range<std::list<int>> &&
14 std::ranges::random_access_range<std::vector<int>> &&
15 std::ranges::contiguous_range<std::vector<int>> &&
16 !std::ranges::contiguous_range<std::list<int>> &&
17 std::ranges::common_range<std::vector<int>>
18 );
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 489
Section 2.6.5
References
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 490
Talks I
1 Mateusz Pusz. C++ Concepts and Ranges. Meeting C++, 2018. Available
online at https://youtu.be/pe05ZWdh0N0.
2 Saar Raz. C++20 Concepts: A Day in the Life. CppCon, 2019. Available
online at https://youtu.be/qawSiMIXtE4.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 491
References I
1 Andrzej Krzemienski. Requires-Expression. Jan. 29, 2020, https:
//akrzemi1.wordpress.com/2020/01/29/requires-expression/.
2 Andrzej Krzemienski. Requires-Clause. March 26, 2020, https:
//akrzemi1.wordpress.com/2020/03/26/requires-clause/.
3 Andrzej Krzemienski. Ordering by Constraints. May 7, 2020,
https://akrzemi1.wordpress.com/2020/05/07/
ordering-by-constraints/
4 Andreas Fertig. How C++20 Concepts Can Simplify Your Code, July 7,
2020, https://andreasfertig.blog/2020/07/how-cpp20-concept
s-can-simplify-your-code/.
5 Andreas Fertig. C++20 Concepts: Testing Constrained Functions, Aug. 4,
2020, https://andreasfertig.blog/2020/08/cpp20-concepts-te
sting-constrained-functions/.
6 Andreas Fertig. C++20 Concepts: Subsumption rules, Sept. 1, 2020,
https://andreasfertig.blog/2020/09/
cpp20-concepts-subsumption-rules/.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 492
Section 2.7
Lambda Expressions
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 493
Motivation for Lambda Expressions
■ functor classes extremely useful, especially for generic programming
. . . . . . . . ..........
■ writing definitions of functor classes somewhat tedious, especially if many
such classes
■ functor classes all have same general structure (i.e., constructor,
function-call operator, zero or more data members)
■ would be nice if functor could be created without need to explicitly write
functor-class definition
■ lambda expressions provide compact notation for creating functors
■ convenience feature (not fundamentally anything new that can be done
with lambda expressions that could not already have been done without
them)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 494
Lambda Expressions
■ lambda expression consists of:
1 introducer: capture list in square brackets
2 declarator: parameter list in parentheses followed by return type using
trailing return-type syntax
3 compound statement in brace brackets
■ capture list specifies objects to be captured as data members
■ declarator specifies parameter list and return type of function-call operator
■ compound statement specifies body of function-call operator
■ if no declarator specified, defaults to ()
■ if no return type specified, defaults to type of expression in return
statement, or void if no return statement
■ when evaluated, lambda expression yields object called closure (which is
essentially a functor)
■ examples:
[](double x)->int{return floor(x);}
[](int x, int y){return x < y;}
[]{std::cout << "Hello, World!\n";}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 495
Lambda Expressions (Continued)
■ closure object is unnamed (temporary object)
■ closure type is unnamed
■ operator() is always inline [C++17 §8.1.5.1/3]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ operator() is const member function unless mutable keyword used
[C++17 §8.1.5.1/4]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ if closure type is literal type, all members of closure type automatically
constexpr
■ if no capture, closure type provides conversion function to pointer to
function having same parameter and return types as closure type’s
function call operator; value returned is address of function that, when
invoked, has same effect as invoking closure type’s function call operator
(function pointer not tied to lifetime of closure object) ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §8.1.5.1/6]
■ although operator() in closure very similar to case of normal functor,
not everything same (e.g., operator() member in closure type cannot
access this pointer for closure type)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 496
Hello World Program Revisited
1 #include <iostream>
2
3 int main() {
4 []{std::cout << "Hello, World!\n";}();
5 }
1 #include <iostream>
2
3 struct Hello {
4 void operator()() const {
5 std::cout << "Hello, World!\n";
6 }
7 };
8
9 int main() {
10 Hello hello;
11 hello();
12
Copyright}© 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 497
Linear-Function Functor Example
1 #include <iostream>
2
3 auto make_linear_func(float a, float b)
4 {return [a, b](float x){return a * x + b;};}
5
6 int main() {
7 float a = 0.5f; float b = 1.0f;
8 auto f = make_linear_func(a, b);
9 std::cout << f(1.0f) << ’\n’;
10 }
1 #include <iostream>
2
3 class linear_func {
4 public:
5 linear_func(float a, float b) : a_(a), b_(b) {}
6 float operator()(float x) const {return a_ * x + b_;}
7 private:
8 float a_; float b_;
9 };
10
11 linear_func make_linear_func(float a, float b)
12 {return linear_func(a, b);}
13
14 int main() {
15 float a = 0.5f; float b = 1.0f;
16 linear_func f = make_linear_func(a, b);
17 std::cout << f(1.0f) << ’\n’;
18 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 498
Comparison Functor Example
1 #include <iostream>
2 #include <algorithm>
3 #include <cstdlib>
4 #include <vector>
5
6 int main() {
7 std::vector<int> v{-3, 3, 4, 0, -2, -1, 2, 1, -4};
8 std::sort(v.begin(), v.end(),
9 [](int x, int y) {return std::abs(x) < std::abs(y);});
10 for (auto x : v) std::cout << x << ’\n’;
11 }
1 #include <iostream>
2 #include <algorithm>
3 #include <cstdlib>
4 #include <vector>
5
6 struct abs_less {
7 bool operator()(int x, int y) const
8 {return std::abs(x) < std::abs(y);}
9 };
10
11 int main() {
12 std::vector<int> v{-3, 3, 4, 0, -2, -1, 2, 1, -4};
13 std::sort(v.begin(), v.end(), abs_less());
14 for (auto x : v) std::cout << x << ’\n’;
15 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 499
Capturing Objects
■ locals only available if captured; non-locals always available
■ can capture by value or by reference
■ different locals can be captured differently
■ can specify default capture mode
■ can explicitly list objects to be captured or not
■ might be wise to explicitly list all objects to be captured (when practical) to
avoid capturing objects accidentally (e.g., due to typos)
■ in member function, to capture class object by value, capture *this
■ in member function, can also capture this
■ this must be captured by value
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 500
std::transform
■ (unary version of) std::transform applies given (unary) operator to
each element in range specified by pair of iterators and writes result to
location specified by another iterator
■ definition of std::transform would typically resemble:
template <class InputIterator, class OutputIterator,
class UnaryOperator>
OutputIterator transform(InputIterator first,
InputIterator last, OutputIterator result,
UnaryOperator op) {
while (first != last) {
*result = op(*first);
++result;
++first;
}
return result;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 501
Modulus Example
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 int main() {
6 int m = 2;
7 std::vector<int> v{0, 1, 2, 3};
8 std::transform(v.begin(), v.end(), v.begin(),
9 [m](int x){return x % m;});
10 for (auto x : v) std::cout << x << ’\n’;
11 }
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 class mod {
6 public:
7 mod(int m_) : m(m_) {}
8 int operator()(int x) const {return x % m;}
9 private:
10 int m;
11 };
12
13 int main() {
14 int m = 2;
15 std::vector<int> v{0, 1, 2, 3};
16 std::transform(v.begin(), v.end(), v.begin(), mod(m));
17 for (auto x : v) std::cout << x << ’\n’;
18 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 502
Modulus Example: Without Lambda Expression
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 class mod {
6 public:
7 mod(int m_) : m(m_) {}
8 int operator()(int x) const {return x % m;}
9 private:
10 int m;
11 };
12
13 int main() {
14 int m = 2;
15 std::vector<int> v{0, 1, 2, 3};
16 std::transform(v.begin(), v.end(), v.begin(), mod(m));
17 for (auto x : v) std::cout << x << ’\n’;
18 }
■ approximately 8.5 lines of code to generate functor
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 503
Modulus Example: With Lambda Expression
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 int main() {
6 int m = 2;
7 std::vector<int> v{0, 1, 2, 3};
8 std::transform(v.begin(), v.end(), v.begin(),
9 [m](int x){return x % m;});
10 for (auto x : v) std::cout << x << ’\n’;
11 }
■ m captured by value
■ approximately 0.5 lines of code to generate functor
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 504
std::for_each
■ std::for_each applies given function/functor to each element in range
specified by pair of iterators
■ definition of std::for_each would typically resemble:
template<class InputIterator, class Function>
Function for_each(InputIterator first,
InputIterator last, Function func) {
while (first != last) {
func(*first);
++first;
}
return move(func);
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 505
Product Example
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 int main() {
6 std::vector<int> v{2, 3, 4};
7 int prod = 1;
8 std::for_each(v.begin(), v.end(),
9 [&prod](int x)->void{prod *= x;});
10 std::cout << prod << ’\n’;
11 }
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 class cum_prod {
6 public:
7 cum_prod(int& prod_) : prod(prod_) {}
8 void operator()(int x) const {prod *= x;}
9 private:
10 int& prod;
11 };
12
13 int main() {
14 std::vector<int> v{2, 3, 4};
15 int prod = 1;
16 std::for_each(v.begin(), v.end(), cum_prod(prod));
17 std::cout << prod << ’\n’;
18 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 506
Product Example: Without Lambda Expression
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 class cum_prod {
6 public:
7 cum_prod(int& prod_) : prod(prod_) {}
8 void operator()(int x) const {prod *= x;}
9 private:
10 int& prod;
11 };
12
13 int main() {
14 std::vector<int> v{2, 3, 4};
15 int prod = 1;
16 std::for_each(v.begin(), v.end(), cum_prod(prod));
17 std::cout << prod << ’\n’;
18 }
■ approximately 8.5 lines of code to generate functor
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 507
Product Example: With Lambda Expression
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 int main() {
6 std::vector<int> v{2, 3, 4};
7 int prod = 1;
8 std::for_each(v.begin(), v.end(),
9 [&prod](int x)->void{prod *= x;});
10 std::cout << prod << ’\n’;
11 }
■ prod captured by reference
■ approximately 1 line of code to generate functor
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 508
More Variations on Capture
double a = 2.14;
double b = 3.14;
double c = 42.0;
// capture all objects by reference (i.e., a, b, and c)
[&](double x, double y){return a * x + b * y + c;}
// capture all objects by value (i.e., a, b, and c)
[=](double x, double y){return a * x + b * y + c;}
// capture all objects by value, except a
// which is captured by reference
[=,&a](double x, double y){return a * x + b * y + c;}
// capture all objects by reference, except a
// which is captured by value
[&,a](double x, double y){return a * x + b * y + c;}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 509
Generalized Lambda Capture
■ can specify name for captured object in closure type
int a = 1;
auto f = [x = a]() {return x;};
■ can capture result of expression (e.g., to perform move instead of copy or
to add arbitrary new state to closure type)
std::vector<int> v(1000, 1);
auto f = [v = std::move(v)]() ->
const std::vector<int>& {return v;};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 510
Generalized Lambda Capture Example
1 #include <iostream>
2
3 int main() {
4 int x = 0;
5 int y = 1;
6 auto f = [&count = x, inc = y + 1](){
7 return count += inc;
8 };
9 std::cout << f() << ’ ’;
10 std::cout << f() << ’\n’;
11 }
12
13 // output: 2 4
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 511
Generic Lambda Expressions
■ can allow compiler to deduce type of lambda function parameters
■ generates closure type with templated function-call operator
■ one template type parameter for each occurrence of auto in lambda
expression’s parameter declaration clause ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §8.1.5.1/3]
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 512
Generic Lambda Expression Example [Generic]
1 #include <iostream>
2 #include <complex>
3 #include <string>
4
5 int main() {
6 using namespace std::literals;
7 auto add = [](auto x, auto y) {return x + y;};
8 std::cout << add(1, 2) << ’ ’ << add(1.0, 2.0) << ’ ’
9 << add(1.0, 2.0i) << ’ ’ << add("Jell"s, "o"s) << ’\n’;
10 }
1 #include <iostream>
2 #include <complex>
3 #include <string>
4
5 struct Add {
6 template <class T, class U>
7 auto operator()(T x, U y) {return x + y;};
8 };
9
10 int main() {
11 using namespace std::literals;
12 Add add;
13 std::cout << add(1, 2) << ’ ’ << add(1.0, 2.0) << ’ ’
14 << add(1.0, 2.0i) << ’ ’ << add("Jell"s, "o"s) << ’\n’;
15 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 513
Generic Lambda Expression Example [Convenience]
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 int main() {
6 std::vector<int> v{0, 1, 2, 3, 4, 5, 6, 7};
7 // sort elements of vector in descending order
8 std::sort(v.begin(), v.end(),
9 [](auto i, auto j) {return i > j;});
10 std::for_each(v.begin(), v.end(),
11 [](auto i) {std::cout << i << ’\n’;});
12 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 514
Example: Generic Lambda With Template Head
1 int main() {
2 // deduced types for x and y are independent
3 auto f = [](auto x, auto y) {
4 // ...
5 };
6 // x and y must have same deduced type
7 auto g = []<class T>(T x, T y) {
8 // ...
9 };
10 f(1, 2);
11 g(1, 2);
12 f(1, 2.0);
13 // g(1, 2.0); // ERROR
14 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 515
Dealing With Unnamed Types
■ fact that closure types unnamed causes complications when need arises
to refer to closure type
■ helpful language features: auto, decltype
■ helpful library features: std::function
■ closures can be stored using auto or std::function
■ closures that do not capture can be “stored” by assigning to function
pointer
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 516
Using auto, decltype, and std::function
1 #include <iostream>
2 #include <functional>
3
4 std::function<double(double)> linear(double a, double b) {
5 return [=](double x){return a * x + b;};
6 }
7
8 int main() {
9 // type of f is std::function<double(double)>
10 auto f = linear(2.0, -1.0);
11 // g has closure type
12 auto g = [](double x){return 2.0 * x - 1.0;};
13 double (*u)(double) = [](double x){return 2.0 * x - 1.0;};
14 // h has same type as g
15 decltype(g) h = g;
16 for (double x = 0.0; x < 10.0; x += 1.0) {
17 std::cout << x << ’ ’ << f(x) << ’ ’ << g(x) <<
18 ’ ’ << h(x) << (*u)(x) << ’\n’;
19 }
20 }
■ applying function-call operator to f much slower than in case of g and h
■ when std::function used, inlining of called function probably not
possible
■ when functor used directly (via function-call operator) inlining is very likely
■ prefer auto over std::function for storing closures
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 517
operator() as Non-const Member
1 #include <iostream>
2
3 int main()
4 {
5 int count = 5;
6 // Must use mutable in order to be able to
7 // modify count member.
8 auto get_count = [count]() mutable -> int {
9 return count++;
10 };
11
12 int c;
13 while ((c = get_count()) < 10) {
14 std::cout << c << ’\n’;
15 }
16 }
■ operator() is declared as const member function unless mutable
keyword used
■ const member function cannot change (non-static) data members
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 518
Constexpr Lambdas
1 #include <iostream>
2 #include <array>
3
4 template <typename T>
5 constexpr auto multiply_by(T i) {
6 return [i](auto j) {return i * j;};
7 // OK: lambda is literal type so members
8 // are automatically constexpr
9 }
10
11 int main() {
12 constexpr auto mult_by_2 = multiply_by(2);
13 std::array<int, mult_by_2(8)> a;
14 std::cout << a.size() << ’\n’;
15 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 519
Comparison Functors for Containers
1 #include <iostream>
2 #include <vector>
3 #include <set>
4
5 int main() {
6 // The following two lines are the only important ones:
7 auto cmp = [](int* x, int* y){return *x < *y;};
8 std::set<int*, decltype(cmp)> s(cmp);
9
10 // Just for something to do:
11 // Print the elements of v in sorted order with
12 // duplicates removed.
13 std::vector<int> v = {4, 1, 3, 2, 1, 1, 1, 1};
14 for (auto& x : v) {
15 s.insert(&x);
16 }
17 for (auto x : s) {
18 std::cout << *x << ’\n’;
19 }
20 }
■ note that s is not default constructed
■ since closure types not default constructible, following would fail:
std::set<int*, decltype(cmp)> s;
■ note use of decltype in order to specify type of functor
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 520
What Could Possibly Go Wrong?
1 #include <iostream>
2 #include <vector>
3 #include <functional>
4
5 std::vector<int> vec{2000, 4000, 6000, 8000, 10000};
6 std::function<int(int)> func;
7
8 void do_stuff()
9 {
10 int modulus = 10000;
11 func = [&](int x){return x % modulus;};
12 for (auto x : vec) {
13 std::cout << func(x) << ’\n’;
14 }
15 }
16
17 int main()
18 {
19 do_stuff();
20 for (auto x : vec) {
21 std::cout << func(x) << ’\n’;
22 }
23 }
■ above code has very serious bug; what is it?
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 521
Dangling References
■ if some objects captured by reference, closure can hold dangling
references
■ responsibility of programmer to avoid such problems
■ if will not cause performance issues, may be advisable to capture by value
(to avoid problem of dangling references)
■ dangling-reference example:
1 #include <iostream>
2 #include <functional>
3
4 std::function<double(double)> linear(double a, double b) {
5 return [&](double x){return a * x + b;};
6 }
7
8 int main() {
9 auto f = linear(2.0, -1.0);
10 // bad things will happen here
11 std::cout << f(1.0) << ’\n’;
12 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 522
Section 2.7.1
References
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 523
Talks I
1 Herb Sutter. Lambdas, Lambdas Everywhere. Professional Developers
Conference (PDC), Redmond, WA, USA, Oct. 27–29, 2010. Available
online at https://youtu.be/rcgRY7sOA58.
2 Herb Sutter. C++0x Lambda Functions. Northwest C++ Users’ Group
(NWCPP), Redmond, WA, USA, May 18, 2011. Available online at
https://vimeo.com/23975522.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 524
Section 2.8
Comparison: Equality/Equivalence and Ordering
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 525
Equality Relations
■ equality relation is rule for set S that specifies for every two elements
a, b ∈ S, if a equals b.
■ for two objects a and b of some type, a and b should be deemed equal if
they represent same logical value
■ consider SimpleString type:
class SimpleString {
bool operator==(const SimpleString& other) const;
// ...
private:
char* start; // start of character buffer
std::size_t length; // length of buffer
};
■ two objects a and b of type SimpleString having same logical value
most likely to be defined as:
2 length members are equal; and
2 buffers pointed to by start members contain identical sequences of
characters (but start members themselves not required to be equal)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 526
Equivalence Relations
■ in some contexts, can be desirable to treat two objects of some type with
unequal values as equivalent (i.e., as if equal)
■ two values said to be equivalent (in some context) if they are to be treated
as if they were same (even though they might not be equal)
■ equivalence relation is rule for set S that specifies for every two elements
a, b ∈ S, if a equivalent b.
■ consider case-insensitive comparison of two std::string objects
■ in this context, strings with values “hello” and “HELLO” are clearly not
equal but must be treated as if so
■ effectively, equivalence relation partitions set into disjoint subsets where
elements of each subset are equivalent
■ these subsets are known as equivalence classes
■ elements deemed equivalent if and only if belong to same equivalence
class
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 527
Ordering Relations
■ ordering relation is rule for set S that specifies, for every two elements
a, b ∈ S, if a has some particular rank relative to b (e.g., precedes or
follows)
■ for example, each of following built-in operators for type int constitutes
ordering relation for set of all int values:
2 less than (<)
2 greater than (>)
2 less than or equal (<=)
2 greater than or equal (>=)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 528
Ordering Relationships
··· x−2 x−1 x0 x1 x2 ···
lesser greater
■ given set {xi } of values of some type T and ordering relations, we can
attempt to place values in order from least to greatest
■ may encounter elements that are incomparable
■ given any two values a and b in set, exactly one of following possibilities
must be true:
1 a precedes b (i.e., a is less than b)
2 a and b are comparable and a does not precede b and b does not precede
a (i.e., a is equivalent to b)
3 b precedes a (i.e., a is greater than b)
4 a and b are incomparable
■ two key considerations going forward:
1 are all values comparable?
2 does equivalence imply equality?
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 529
Comparison Categories
■ as of C++20, language provides mechanism for querying (full) ordering
relationship between two values
■ depending on properties of ordering relations used for set of values of
some type, different kinds of outcomes can arise when comparing
elements of set
■ different kinds of outcomes that can arise addressed in language by
comparison categories
■ each comparison category is associated with certain key properties that
comparison operation must satisfy
■ some comparison categories are stronger (i.e., have stricter requirements)
than others
■ language defines following three comparison categories in order of
decreasing strength:
1 strong ordering
2 weak ordering
3 partial ordering
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 530
Strong Orderings (1)
■ comparison category that is strongest (i.e., has strictest requirements) is
strong ordering [C++20 §17.11.2.4]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ strong ordering closely related to notion of total order in set theory
............
■ two distinguishing characteristics of strong ordering:
1 every two elements in set are comparable
2 equivalence of elements with respect to ordering relation implies equality
■ given any two elements a and b in set, exactly one of following three
possibilities must be true:
1 a precedes b (i.e., a is less than b)
2 a and b are comparable and a does not precede b and b does not precede
a (i.e., a is equal to b)
3 b precedes a (i.e., a is greater than b)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 531
Strong Orderings (2)
■ ordering relationship between values associated with strong ordering
represented by std::strong_ordering class
■ std::strong_ordering class defines following members to represent
ordering relationship:
2 less
2 equal and equivalent
2 greater
■ for example, for set of int values, built-in comparison operators are
consistent with strong ordering
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 532
Weak Orderings (1)
■ second strongest comparison category is weak ordering ⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++20 §17.11.2.3]
■ weak ordering related to notion of total preorder in set theory
................
■ two distinguishing characteristics of weak ordering:
1 every two elements in set are comparable
2 equivalence of elements with respect to ordering relation does not
necessarily imply equality
■ given any two elements a and b in set, exactly one of following three
possibilities must be true:
1 a precedes b (i.e., a is less than b)
2 a and b are comparable and a does not precede b and b does not precede
a (i.e., a is equivalent to b)
3 b precedes a (i.e., a is greater than b)
■ two elements that are related by neither less-than nor greater-than
relationship are equivalent (since not necessarily equal)
■ strong ordering satisfies requirements of weak ordering
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 533
Weak Orderings (2)
■ ordering relationship between values associated with weak ordering
represented by std::weak_ordering class
■ std::weak_ordering class defines following members to represent
ordering relationship:
2 less
2 equivalent
2 greater
■ for set of null-terminated strings, case-insensitive string comparison
performed by strcasecmp function (in POSIX standard) provides weak
ordering
■ case-insensitive comparison would deem values "hello" and "HELLO"
to be equivalent (even though these values not equal)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 534
Partial Orderings (1)
■ comparison category that is weakest (i.e., has least strict requirements) is
partial ordering [C++20 §17.11.2.2]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ partial ordering related to notion of ..........
preorder in set theory
■ two distinguishing characteristics of partial ordering:
1 every two elements in set are not necessarily comparable
2 equivalence of elements with respect to ordering relation does not
necessarily imply equality
■ given any two elements a and b in set, exactly one of following possibilities
must be true:
1 a precedes b (i.e., a is less than b)
2 a and b are comparable and a does not precede b and b does not precede
a (i.e., a is equivalent to b)
3 b precedes a (i.e., a is greater than b)
4 a and b are incomparable
■ strong and weak orderings satisfy requirements of partial ordering
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 535
Partial Orderings (2)
■ ordering relationship between values associated with partial ordering
represented by class std::partial_ordering
■ std::partial_ordering class defines following members to represent
ordering relationship:
2 less
2 equivalent
2 greater
2 unordered
■ for example, for set of double values, built-in comparison operators
consistent with partial ordering (since NaN values are incomparable with
other double values and each other)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 536
Hierarchy of Comparison Categories
strong_ordering weak_ordering partial_ordering
■ comparison categories in decreasing order of strength:
1 strong_ordering
2 weak_ordering
3 partial_ordering
■ can implicitly convert from stronger comparison category to weaker one
■ as shown in table below, value in stronger category mapped to value of
same name in weaker category, except case of equal, which becomes
equivalent
Conversion of Ordering Category Values∗
Category Corresponding Values
strong_ordering less equal/equivalent greater —
weak_ordering less equivalent greater —
partial_ordering less equivalent greater unordered
∗ can only convert in stronger-to-weaker (i.e., downward) direction
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 537
Two-Way Comparison Operators
Ordering Operators
Equality/Equivalence Operator Name
Operators
Operator Name < less than
<= less than or equal
== equality
> greater than
!= inequality
>= greater than or equal
■ single equality/ordering operator does not give full information about
ordering relationship
■ if one of equality/ordering operators returns false, cannot tell relationship
between operands without applying further ordering operators
■ for example, if a < b is false, we do not know if: 1) a == b is true;
2) a > b is true; or 3) a and b are incomparable
■ similarly, if a == b is false, we do not know if: 1) a < b is true; 2) a > b
is true; or 3) a and b are incomparable
■ incomplete nature of information provided by two-way comparison
operator serves as motivation for three-way comparison operator
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 538
Three-Way Comparison (a.k.a. Spaceship) Operator (1)
■ three-way comparison (a.k.a. spaceship) operator <=> is binary operator
that returns complete ordering relationship between two values (i.e., less,
equal/equivalent, greater, or incomparable)
■ return value of operator corresponds to type associated with comparison
category realized by operator:
Category Return Type
strong ordering std::strong_ordering
weak ordering std::weak_ordering
partial ordering std::partial_ordering
■ result of three-way comparison operator is as follows:
Relationship Between a and b Result∗ of a <=> b
a is less than b less
a and b are equal/equivalent equal/equivalent
a is greater than b greater
a and b are incomparable unordered
∗ value in corresponding comparison-category class
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 539
Three-Way Comparison (a.k.a. Spaceship) Operator (2)
■ reversing order of operands for three-way comparison operator only has
effect of swapping less and greater:
Result of a <=> b Result of b <=> a
less greater
equal/equivalent equal/equivalent
greater less
unordered unordered
■ integral types yield strong_ordering
■ floating-point types yield partial_ordering
■ pointer types yield strong_ordering whose result is unspecified if
pointers being compared do not point into same array
■ example:
static_assert(2 <=> 1 == std::strong_ordering::greater);
static_assert(1.0 <=> 2.0 == std::partial_ordering::less);
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 540
Three-Way Comparison Example
1 #include <cassert>
2 #include <compare>
3 #include <concepts>
4 #include <limits>
5 #include <numbers>
6
7 int main() {
8 constexpr int one = 1;
9 constexpr int two = 2;
10 static_assert(std::numeric_limits<double>::is_iec559);
11 constexpr double nan = std::numeric_limits<double>::quiet_NaN();
12 constexpr double half = 0.5;
13 constexpr double pi = std::numbers::pi_v<double>;
14 static_assert((std::same_as<decltype(one <=> two),
15 std::strong_ordering>));
16 static_assert(one <=> two == std::strong_ordering::less);
17 static_assert(one <=> one == std::strong_ordering::equal);
18 static_assert(two <=> one == std::strong_ordering::greater);
19 static_assert((std::same_as<decltype(half <=> pi),
20 std::partial_ordering>));
21 static_assert(half <=> pi == std::partial_ordering::less);
22 static_assert(half <=> half == std::partial_ordering::equivalent);
23 static_assert(pi <=> half == std::partial_ordering::greater);
24 assert(half <=> nan == std::partial_ordering::unordered);
25 static_assert((std::same_as<decltype(0.5 <=> 1),
26 std::partial_ordering>));
27 static_assert(0.5 <=> 1 == std::partial_ordering::less);
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 541
Comparing Ordering Result and Literal Zero (1)
■ value v of comparison-category type can be compared to literal 0 (where v
would typically result from evaluating expression of form a <=> b)
■ when literal 0 is second operand, this has following meaning:
Expression Ordering Equivalent Expression†
v == 0 strong v == C::equal
weak/partial v == C::equivalent
v != 0 strong !(v == C::equal)
weak/partial !(v == C::equivalent)
v < 0 — v == C::less
v > 0 — v == C::greater
v <= 0 strong v == C::less || v == C::equal
weak/partial v == C::less || v == C::equivalent
v >= 0 strong v == C::greater || v == C::equal
weak/partial v == C::greater || v == C::equivalent
v <=> 0 — v
†C is comparison-category type (i.e., strong_ordering, weak_ordering, partial_ordering)
■ abbreviated syntax above allows for less verbose code when extracting
two-way comparison result from three-way comparison result
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 542
Comparing Ordering Result and Literal Zero (2)
■ reversing order of operands for three-way comparison operator only has
effect of swapping less and greater:
less if v = greater
{︃
reverse(v) = greater if v = less
v otherwise
■ for each relational operator op (i.e., equality, inequality, less-than,
greater-than, less-than-or-equal, greater-than-or-equal):
0 op v is equivalent to reverse(v) op 0
■ consequently, when literal 0 is first operand, we have:
Expression Equivalent Expression Simplified
0 == v reverse(v) == 0 v == 0
0 != v reverse(v) != 0 v != 0
0 < v reverse(v) < 0 v > 0
0 > v reverse(v) > 0 v < 0
0 <= v reverse(v) <= 0 v >= 0
0 >= v reverse(v) >= 0 v <= 0
0 <=> v reverse(v) <=> 0 reverse(v)
■ moreover, a <=> b equivalent to 0 <=> (b <=> a)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 543
Three-Way Comparison Example
1 #include <cassert>
2 #include <compare>
3 #include <concepts>
4 #include <limits>
5 #include <numbers>
6
7 int main() {
8 constexpr int one = 1;
9 constexpr int two = 2;
10 static_assert(std::numeric_limits<double>::is_iec559);
11 constexpr double nan = std::numeric_limits<double>::quiet_NaN();
12 constexpr double half = 0.5;
13 constexpr double pi = std::numbers::pi_v<double>;
14 static_assert((std::same_as<decltype(one <=> two),
15 std::strong_ordering>));
16 static_assert(one <=> two < 0);
17 static_assert(one <=> one == 0);
18 static_assert(two <=> one > 0);
19 static_assert((std::same_as<decltype(half <=> pi),
20 std::partial_ordering>));
21 static_assert(half <=> pi < 0);
22 static_assert(half <=> half == 0);
23 static_assert(pi <=> half > 0);
24 assert(half <=> nan == std::partial_ordering::unordered);
25 assert(!(half <=> nan == 0) && !(half <=> nan < 0) && !(half <=> nan > 0));
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 544
Named Comparison Functions
Function∗ Corresponding Relational Operator
equality/equivalence
is_eq
inequality/inequivalence
is_neq
less than
is_lt
greater than
is_gt
less than or equal
is_lteq
greater than or equal
is_gteq
∗ in namespace std
■ can use above functions to convert result of three-way comparison to
various two-way comparisons ⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++20 §17.11.1]
■ example:
#include <compare>
static_assert(std::is_lt(1 <=> 2));
static_assert(std::is_gt(42.0 <=> 0.0));
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 545
Customization-Point Objects (CPOs) for Ordering
■ standard library provides customization-point objects (CPOs) that allow
comparisons to be performed in alternative manner for particular type
■ functionality provided via std::strong_order, std::weak_order, and
std::partial_order [C++20 §17.11.6]
⁓⁓⁓⁓⁓⁓⁓⁓
■ user provides function (i.e., strong_order, weak_order, or
partial_order) that is found via argument-dependent lookup (ADL)
■ std::strong_order, std::weak_order, and std::partial_order
also provide alternative comparison schemes for floating-point types
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 546
std::strong_order Example: Library Code
1 #include <compare>
2
3 namespace foo {
4
5 class Point {
6 public:
7 Point(int x, int y) : x_(x), y_(y) {}
8 int x() const {return x_;}
9 int y() const {return y_;}
10 bool operator==(const Point& other) const = default;
11 // note: no operator<=> provided
12 private:
13 int x_;
14 int y_;
15 };
16
17 inline std::strong_ordering
18 strong_order(const Point& p, const Point& q) {
19 if (auto result = p.x() <=> q.x(); result != 0)
20 {return result;}
21 return p.y() <=> q.y();
22 }
23
24 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 547
std::strong_order Example: User Code
1 #include <compare>
2 #include <iostream>
3 #include <map>
4 #include "custom_order_3.hpp"
5
6 struct Point_compare {
7 bool operator()(const foo::Point& p, const foo::Point& q)
8 const {
9 return std::strong_order(p, q) < 0;
10 // invokes foo::strong_order via ADL
11 }
12 };
13
14 int main() {
15 std::map<foo::Point, int, Point_compare> m({
16 {{1, 1}, 3},
17 {{1, 0}, 2},
18 {{0, 1}, 1},
19 {{0, 0}, 0},
20 });
21 for (auto&& [p, v] : m) {
22 std::cout << p.x() << ’ ’ << p.y() << ’ ’ << v << ’\n’;
23 }
24 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 548
Taxonomy of Relational Operators
Equality Ordering
Primary equality (==) three-way comparison (<=>)
Secondary inequality (!=) less than (<)
greater than (>)
less than or equal (<=)
greater than or equal (>=)
■ each relational operator can be classified in two ways:
2 primary or secondary
2 equality or ordering
■ primary operators have ability to be reversed (e.g., a == b versus
b == a)
■ secondary operators have ability to be rewritten in terms of corresponding
primary operator (e.g., a > b versus a <=> b > 0)
■ rewriting performs transformation to source code does not generate new
functions
■ primary operators never rewritten in terms of one another
■ primary and secondary operators can be defaulted
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 549
Relational Operator Reversing and Rewriting
■ primary relational operators can be reversed according to following rules:
Original Reversed
a == b b == a
a <=> b 0 <=> (b <=> a)
■ secondary relational operators can be rewritten in terms of primary
relational operators according to following rules (where first alternate
preferred over second):
Original First Alternate Second Alternate
a != b !(a == b) !(b == a)
a < b a <=> b < 0 0 < b <=> a
a > b a <=> b > 0 0 > b <=> a
a <= b a <=> b <= 0 0 <= b <=> a
a >= b a <=> b >= 0 0 >= b <=> a
■ allowing reversing and rewriting of relational operators has potential to
(greatly) reduce number of functions that programmer must provide
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 550
Overload Resolution
■ candidate lookup considers all operators of that name as well as all
reversals and rewrites in one shot
■ non-reversed candidate preferred over reversed one
■ for example, for evaluating a == b, a == b preferred over b == a
■ non-rewritten candidate preferred over rewritten one
■ for example, for evaluating a < b, a < b preferred over a <=> b < 0
■ if best viable candidate is either rewritten or reversed and is invalid (e.g.,
due to incorrect return type), code is invalid
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 551
Example: Equality as Member
1 #include <cassert>
2
3 class Widget {
4 public:
5 explicit Widget(int i) : i_(i) {}
6 int value() const {return i_;}
7 /* A */ bool operator==(const Widget& other) const
8 {return i_ == other.i_;}
9 /* B */ bool operator==(int i) const {return i_ == i;}
10 private:
11 int i_;
12 };
13
14 int main() {
15 assert(Widget(42) == Widget(42)); // calls A
16 assert(Widget(42) == 42); // calls B
17 assert(42 == Widget(42));
18 // reverse to Widget(42) == 42; calls B
19 assert(Widget(42) != Widget(0));
20 // rewrite as !(Widget(42) == Widget(0)); calls A
21 assert(Widget(42) != 0);
22 // rewrite as !(Widget(42) == 0); calls B
23 assert(42 != Widget(0));
24 // rewrite swapped as !(Widget(0) == 42); calls B
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 552
Example: Equality as Nonmember
1 #include <cassert>
2
3 class Widget {
4 public:
5 explicit Widget(int i) : i_(i) {}
6 int value() const {return i_;}
7 private:
8 int i_;
9 };
10
11 /* A */ bool operator==(const Widget& a, const Widget& b)
12 {return a.value() == b.value();}
13 /* B */ bool operator==(const Widget& a, int i)
14 {return a.value() == i;}
15
16 int main() {
17 assert(Widget(42) == Widget(42)); // calls A
18 assert(Widget(42) == 42); // calls B
19 assert(42 == Widget(42));
20 // reverse to Widget(42) == 42; calls B
21 assert(Widget(42) != Widget(0));
22 // rewrite as !(Widget(42) == Widget(0)); calls A
23 assert(Widget(42) != 0);
24 // rewrite as !(Widget(42) == 0)); calls B
25 assert(42 != Widget(0));
26 // rewrite swapped as !(Widget(0) == 42); calls B
27 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 553
Example: Both Primary Operators as Members
1 #include <cassert>
2 #include <compare>
3
4 class Widget {
5 public:
6 explicit Widget(int i) : i_(i) {}
7 int value() const {return i_;}
8 /* A */ bool operator==(const Widget& other) const
9 {return i_ == other.i_;}
10 /* B */ std::strong_ordering operator<=>(const Widget& other) const
11 {return i_ <=> other.i_;}
12 /* C */ bool operator==(int i) const {return i_ == i;}
13 /* D */ std::strong_ordering operator<=>(int i) const {return i_ <=> i;}
14 private:
15 int i_;
16 };
17
18 int main() {
19 assert(Widget(0) == Widget(0)); // calls A
20 assert(Widget(1) != 0);
21 // rewrite to !(Widget(1) == 0); calls C
22 assert(Widget(-1) < Widget(0));
23 // rewrite to Widget(-1) <=> Widget(0) < 0; calls B
24 assert(1 > Widget(0));
25 // rewrite swapped to 0 > Widget(0) <=> 1; calls D
26 assert(Widget(-1) < 0);
27 // rewrite to Widget(-1) <=> 0 < 0; calls D
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 554
Example: More Overload Resolution
1 #include <cassert>
2 #include <compare>
3
4 class Widget {
5 public:
6 explicit Widget(int i) : i_(i) {}
7 int value() const {return i_;}
8 /* A */ bool operator==(int i) const {return i_ == i;}
9 /* B */ std::strong_ordering operator<=>(int i) const {return i_ <=> i;}
10 /* C */ bool operator!=(int i) const {return i_ != i;}
11 /* D */ bool operator>(int i) const {return i_ > i;}
12 private:
13 int i_;
14 };
15
16 /* E */ std::strong_ordering operator<=>(int i, const Widget& w)
17 {return i <=> w.value();}
18 /* F */ bool operator>(int i, const Widget& w) {return i > w.value();}
19
20 int main() {
21 assert(Widget(1) != 0); // chooses C (from candidates A, C)
22 assert(0 != Widget(1)); // chooses A (only candidate)
23 assert(1 > Widget(0)); // chooses F (from candidates B, E, F)
24 assert(Widget(0) == 0); // chooses A (only candidate)
25 assert(Widget(1) > 0); // chooses D (from candidates B, D, E)
26 assert(Widget(-1) < 0); // chooses B (from candidates B, E)
27 assert(0 >= Widget(-1)); // chooses E (from candidates B, E)
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 555
Defaulted Primary Relational Operators
■ defaulted equality operator performs memberwise equality test of data
members (and bases) using appropriate equality operators
■ defaulted three-way comparison operator performs lexicographic
comparison of members (and bases) using appropriate three-way
comparison operators
■ defaulted three-way comparison operator never automatically provided
(in part because compiler does not know correct comparison category to
use)
■ if defaulted three-way comparison operator desired, must explicitly define
as default
■ defaulted equality operator only automatically provided if:
2 no equality operator provided; and
2 three-way comparison operator is (explicitly) defaulted
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 556
Example: Without Defaulted Relational Operators
1 #include <compare>
2
3 class Point {
4 public:
5 constexpr Point(int x, int y) : x_(x), y_(y) {}
6 constexpr int x() const {return x_;}
7 constexpr int y() const {return y_;}
8 constexpr bool operator==(const Point& other) const
9 {return x_ == other.x_ && y_ == other.y_;}
10 constexpr std::strong_ordering operator<=>(const Point& other)
11 const {
12 return x_ < other.x_ ? std::strong_ordering::less :
13 (x_ == other.x_ ? y_ <=> other.y_ :
14 std::strong_ordering::greater);
15 }
16 private:
17 int x_;
18 int y_;
19 };
20
21 static_assert(Point(-1, 0) < Point(0, 0));
22 static_assert(Point(0, 1) > Point(0, 0));
23 static_assert(Point(0, -1) < Point(0, 0));
24 static_assert(Point(0, 0) == Point(0, 0));
25 static_assert(Point(1, 0) != Point(0, 0));
26 static_assert(Point(1, 0) > Point(0, 0));
27 static_assert(Point(0, 0) <=> Point(0, 0) == 0);
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 557
Example: With Defaulted Relational Operators
1 #include <compare>
2
3 class Point {
4 public:
5 constexpr Point(int x, int y) : x_(x), y_(y) {}
6 constexpr int x() const {return x_;}
7 constexpr int y() const {return y_;}
8 // The following line can be omitted since operator<=> is defaulted:
9 // bool operator==(const Point&) const = default;
10 std::strong_ordering operator<=>(const Point&) const = default;
11 private:
12 int x_;
13 int y_;
14 };
15
16 static_assert(Point(-1, 0) < Point(0, 0));
17 static_assert(Point(0, 1) > Point(0, 0));
18 static_assert(Point(0, -1) < Point(0, 0));
19 static_assert(Point(0, 0) == Point(0, 0));
20 static_assert(Point(1, 0) != Point(0, 0));
21 static_assert(Point(1, 0) > Point(0, 0));
22 static_assert(Point(0, 0) <=> Point(0, 0) == 0);
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 558
Defaulted Secondary Relational Operators
■ defaulted secondary relational operators are specified in terms of primary
operators; that is,
2 for ordering operator @: x @ y becomes x <=> y @ 0 or 0 @ y <=> x
2 for inequality operator: x != y becomes !(x == y) or !(y == x)
■ defaulted secondary operator must have return type bool
■ if defaulted secondary operator desired, must explicitly define as default
■ normally, sufficient to provide primary relational operators (i.e., equality
and three-way comparison)
■ defaulted secondary operators useful in order to create functions whose
address can be taken (in cases where this is needed)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 559
Example: Defaulted Secondary Relational Operators
1 #include <compare>
2
3 class Widget {
4 public:
5 constexpr Widget(int i = 0) : i_(i) {}
6 Widget(const Widget& other) = default;
7 Widget& operator=(const Widget& other) = default;
8 bool operator==(const Widget& other) const = default;
9 constexpr std::strong_ordering operator<=>(const Widget& other)
10 const = default;
11 bool operator!=(const Widget& other) const = default;
12 bool operator<(const Widget& other) const = default;
13 bool operator<=(const Widget& other) const = default;
14 // note: operator> and operator>= not defaulted
15 private:
16 int i_;
17 };
18
19 constexpr Widget zero(0);
20 constexpr Widget one(1);
21 static_assert(zero < one && zero <= zero); // OK: defaulted members
22 static_assert(one > zero && one >= one); // OK: operators rewritten
23 static_assert(zero == zero && zero != one);
24 auto ptr1 = &Widget::operator<;
25 // OK: function exists for which to take address
26 // auto ptr2 = &Widget::operator>;
27 // ERROR: no function exists for which to take address
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 560
Inconsistent Relational Operators Example
1 #include <compare>
2
3 class Gadget;
4
5 class Widget {
6 public:
7 explicit constexpr Widget(int x) : x_(x) {}
8 constexpr int value() const {return x_;}
9 constexpr bool operator==(const Gadget& other) const;
10 private:
11 int x_;
12 };
13
14 class Gadget {
15 public:
16 explicit constexpr Gadget(int x) : x_(x) {}
17 constexpr int value() const {return x_;}
18 constexpr bool operator==(const Widget& other) const {return true;}
19 private:
20 int x_;
21 };
22
23 constexpr bool Widget::operator==(const Gadget& other) const
24 {return x_ == other.value();}
25
26 static_assert(Widget(1) == Gadget(1));
27 static_assert(Widget(1) != Gadget(2)); // A and B not equal
28 static_assert(Gadget(2) == Widget(1)); // INCONSISTENT: B and A are equal
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 561
Two-Way Versus Three-Way Comparisons
■ in some cases, two-way comparisons are much less efficient to implement
indirectly in terms of three-way comparison
■ this difference is often most pronounced when testing for equality
■ for example, consider testing two ranges of n elements of type T for
equality (such as when comparing two std::vector<T> objects)
■ when testing for equality directly using equality operator:
2 if sizes of ranges do not match, can immediately conclude not equal without
need to iterate over any elements of range
■ when testing for equality indirectly using three-way comparison operator:
2 if sizes of ranges do not match, must still iterate over elements until
corresponding elements not equal or end of shorter range hit to determine if
less or greater case applies
■ for reasons of efficiency, may sometimes be necessary to define some
secondary relational operators
■ when performing comparison, should always prefer two-way (over
three-way) comparison, unless full three-way comparison result needed
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 562
Guidelines on Relational Operators
■ normally, provide only primary relational operators for type
■ only provide secondary relational operators when needed for reasons of
efficiency
■ provide primary relational operators as member functions (as opposed to
nonmember functions), unless compelling reason to do otherwise
■ in generic code use of three-way comparison operator can be problematic,
since types involved may not provide three-way comparison operator
■ for this reason, advisable to use function similar in spirit to
synth_three_way (presented in later example) instead of three-way
comparison operator
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 563
Example: synth_three_way
1 #include <compare>
2 #include <concepts>
3
4 template<class T, class U>
5 constexpr auto synth_three_way(const T& t, const U& u)
6 requires requires {
7 {t < u} -> std::same_as<bool>;
8 {u < t} -> std::same_as<bool>;
9 }
10 {
11 if constexpr(std::three_way_comparable_with<T, U>) {
12 return t <=> u;
13 } else {
14 // assume at least a weak ordering
15 if (t < u) {return std::weak_ordering::less;}
16 if (u < t) {return std::weak_ordering::greater;}
17 return std::weak_ordering::equivalent;
18 }
19 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 564
Example: Use of synth_three_way
1 #include <iostream>
2 #include <string>
3 #include "synth_three_way.hpp"
4
5 // legacy type with no operator<=>
6 struct legacy {
7 bool operator<(const legacy& other) const {return false;}
8 // ... (no operator <=>)
9 };
10
11 // some generic code that needs to use three-way comparison
12 template<class T> void print(const T& a, const T& b) {
13 // auto c = a <=> b;
14 // BAD: will not compile if T does not have operator<=>
15 auto c = synth_three_way(a, b); // GOOD
16 if (c < 0) {std::cout << "less\n";}
17 else if (c == 0) {std::cout << "equal/equivalent\n";}
18 else if (c > 0) {std::cout << "greater\n";}
19 else {std::cout << "unordered\n";}
20 }
21
22 int main() {
23 print(1, 2);
24 print(std::string("b"), std::string("a"));
25 print(legacy{}, legacy{}); // GOOD: no compile error from print
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 565
Example: Integer Type With NaN Representation
1 #include <compare>
2 #include <optional>
3
4 class optint {
5 public:
6 constexpr optint() = default;
7 constexpr explicit optint(int i) : v_(i) {}
8 constexpr bool operator==(const optint& other) const {
9 return has_value() && other.has_value() && *v_ == *other.v_;
10 }
11 constexpr std::partial_ordering operator<=>(const optint& other) const {
12 if (!has_value() || !other.has_value()) {
13 return std::partial_ordering::unordered;
14 } else {return *v_ <=> *other.v_;}
15 }
16 constexpr bool has_value() const {return v_.has_value();}
17 constexpr int value() const {return *v_;}
18 static const optint nan;
19 private:
20 std::optional<int> v_;
21 };
22 constexpr const optint optint::nan = optint{};
23
24 static_assert(optint(0) == optint(0));
25 static_assert(optint(-1) < optint(0));
26 static_assert(optint(1) > optint(0));
27 static_assert(optint(1) != optint(0));
28 static_assert(optint(0) <=> optint::nan == std::partial_ordering::unordered);
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 566
Talks I
1 Jonathan Muller. Using C++20’s Three-way Comparison <=>. CppCon,
Sept. 20, 2019. Available online at https://youtu.be/8jNXy3K2Wpk.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 567
References I
1 Barry Revzin. Comparisons in C++20. July 28, 2019, https:
//brevzin.github.io/c++/2019/07/28/comparisons-cpp20/.
2 Jonathan Muller. June 20, 2018, Mathematics Behind Comparison #1:
Equality and Equivalence Relations.
https://foonathan.net/2018/06/equivalence-relations/.
3 Jonathan Muller. Mathematics Behind Comparison #2: Ordering Relations
in Math. July 19, 2018,
https://foonathan.net/2018/07/ordering-relations-math/.
4 Jonathan Muller. Mathematics Behind Comparison #3: Ordering Relations
in C++. July 19, 2018, https:
//foonathan.net/2018/07/ordering-relations-programming/.
5 Jonathan Muller. Mathematics Behind Comparison #4: Three-Way
Comparison Sept. 7, 2018,
https://foonathan.net/2018/09/three-way-comparison/.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 568
References II
6 Jonathan Muller. Mathematics Behind Comparison #5: Ordering
Algorithms. Oct. 1, 2018,
https://foonathan.net/2018/10/ordering-algorithms/.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 569
Section 2.9
Classes and Inheritance
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 570
Section 2.9.1
Derived Classes and Class Hierarchies
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 571
Derived Classes
■ sometimes, want to express commonality between classes
■ want to create new class from existing class by adding new members or
replacing (i.e., hiding/overriding) existing members
■ can be achieved through language feature known as inheritance
■ generate new class with all members of already existing class, excluding
special member functions (i.e., constructors, assignment operators, and
destructor)
■ new class called derived class and original class called base class
■ derived class said to inherit from base class
■ can add new members (not in base class) to derived class
■ can hide or override member functions from base class with new version
■ syntax for specifying derived class:
class derived_class : base_class_specifiers
■ derived_class is name of derived class; base_class_specifiers provide
base-class information
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 572
Derived Classes (Continued)
■ can more clearly express intent by explicitly identifying relationship
between classes
■ can facilitate code reuse by leverage existing code
■ interface inheritance: allow different derived classes to be used
interchangeably through interface provided by common base class
■ implementation inheritance: save implementation effort by sharing
capabilities provided by base class
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 573
Person Class
1 #include <string>
2
3 class Person {
4 public:
5 Person(const std::string& family_name,
6 const std::string& given_name) :
7 family_name_(family_name), given_name_(given_name) {}
8 std::string family_name() const {return family_name_;}
9 std::string given_name() const {return given_name_;}
10 std::string full_name() const
11 {return family_name_ + ", " + given_name_;}
12 // ...
13 private:
14 std::string family_name_;
15 std::string given_name_;
16 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 574
Student Class Without Inheritance
1 #include <string>
2
3 class Student {
4 public:
5 Student(const std::string& family_name,
6 const std::string& given_name) :
7 family_name_(family_name), given_name_(given_name) {}
8 // NEW
9 std::string family_name() const {return family_name_;}
10 std::string given_name() const {return given_name_;}
11 std::string full_name() const
12 {return family_name_ + ", " + given_name_;}
13 std::string student_id() {return student_id_;} // NEW
14 private:
15 std::string family_name_;
16 std::string given_name_;
17 std::string student_id_; // NEW
18 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 575
Student Class With Inheritance
1 // include definition of Person class here
2
3 class Student : public Person {
4 public:
5 Student(const std::string& family_name,
6 const std::string& given_name,
7 const std::string& student_id) :
8 Person(family_name, given_name),
9 student_id_(student_id) {}
10 std::string student_id() {return student_id_;}
11 private:
12 std::string student_id_;
13 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 576
Complete Inheritance Example
1 #include <string>
2
3 class Person {
4 public:
5 Person(const std::string& family_name,
6 const std::string& given_name) :
7 family_name_(family_name), given_name_(given_name) {}
8 std::string family_name() const {return family_name_;}
9 std::string given_name() const {return given_name_;}
10 std::string full_name() const
11 {return family_name_ + ", " + given_name_;}
12 // ... (including virtual destructor)
13 private:
14 std::string family_name_;
15 std::string given_name_;
16 };
17
18 class Student : public Person {
19 public:
20 Student(const std::string& family_name,
21 const std::string& given_name,
22 const std::string& student_id) :
23 Person(family_name, given_name),
24 student_id_(student_id) {}
25 std::string student_id() {return student_id_;}
26 private:
27 std::string student_id_;
28 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 577
Class Hierarchies
■ inheritance relationships between classes form what is called class
hierarchy
■ often class hierarchy represented by directed (acyclic) graph, where nodes
correspond to classes and edges correspond to inheritance relationships
■ class definitions:
class A { /* ... */ };
class B : public A { /* ... */ };
class C : public A { /* ... */ };
class D : public B { /* ... */ };
class E : public B { /* ... */ };
■ inheritance diagram:
A
B C
D E
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 578
Class Hierarchy Example
■ class definitions:
class Person { /* ... */ };
class Employee : public Person { /* ... */ };
class Student : public Person { /* ... */ };
class Alumnus : public Person { /* ... */ };
class Faculty : public Employee { /* ... */ };
class Staff : public Employee { /* ... */ };
class Grad : public Student { /* ... */ };
class Undergrad : public Student { /* ... */ };
■ inheritance diagram:
Person
Employee Student Alumnus
Faculty Staff Undergrad Grad
■ each of Employee, Student, and Alumnus is a Person; each of Faculty
and Staff is an Employee; each of Undergrad and Grad is a Student
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 579
Member Access Specifiers: protected
■ earlier, introduced public and private access specifiers for class
members
■ in context of inheritance, another access specifier becomes relevant,
namely, protected
■ member declared in protected section of class can only be accessed by
2 member functions and friends of that class; and
2 by member functions and friends of derived classes
■ protected members used to provide developers of derived classes access
to some inner workings of base class without exposing such inner
workings to everyone
■ usually, bad idea to use protected access for data members (for similar
reasons that using public access for data members is usually bad)
■ protected access usually employed for function members
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 580
Types of Inheritance
■ three types of inheritance with respect to access protection: public,
protected, and private
■ these three types of inheritance differ in terms of accessibility, in derived
class, of members inherited from base class
■ private parts of base class are always inaccessible in derived class,
regardless of whether public, protected, or private inheritance used
■ if this were not case, all access protection could simply be bypassed by
using inheritance
■ access specifiers for members accessible in derived class chosen as
follows:
Access Specifier in Derived Class
Access Specifier in Public Protected Private
Base Class Inheritance Inheritance Inheritance
public public protected private
protected protected protected private
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 581
Types of Inheritance (Continued)
■ for struct, defaults to public inheritance
■ for class, defaults to private inheritance
■ public and protected/private inheritance have different use cases, as we
will see later
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 582
Inheritance and Member Access Example
1 class Base {
2 public:
3 void f();
4 protected:
5 void g();
6 private:
7 int x;
8 };
9
10 class Derived_1 : public Base {
11 // f is public
12 // g is protected
13 // x is not accessible from Derived_1
14 };
15
16 class Derived_2 : protected Base {
17 // f is protected
18 // g is protected
19 // x is not accessible from Derived_2
20 };
21
22 class Derived_3 : private Base {
23 // f is private
24 // g is private
25 // x is not accessible from Derived_3
26 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 583
Public Inheritance Example
1 class Base {
2 public:
3 void func_1();
4 protected:
5 void func_2();
6 private:
7 int x_;
8 };
9
10 class Derived : public Base {
11 public:
12 void func_3() {
13 func_1(); // OK
14 func_2(); // OK
15 x_ = 0; // ERROR: inaccessible
16 }
17 };
18
19 struct Widget : public Derived {
20 void func_4() { func_2(); } // OK
21 };
22
23 int main() {
24 Derived d;
25 d.func_1(); // OK
26 d.func_2(); // ERROR: inaccessible
27 d.x_ = 0; // ERROR: inaccessible
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 584
Protected Inheritance Example
1 class Base {
2 public:
3 void func_1();
4 protected:
5 void func_2();
6 private:
7 int x_;
8 };
9
10 class Derived : protected Base {
11 public:
12 void func_3() {
13 func_1(); // OK
14 func_2(); // OK
15 x_ = 0; // ERROR: inaccessible
16 }
17 };
18
19 struct Widget : public Derived {
20 void func_4() { func_2(); } // OK
21 };
22
23 int main() {
24 Derived d; // OK: defaulted constructor is public
25 d.func_1(); // ERROR: inaccessible
26 d.func_2(); // ERROR: inaccessible
27 d.x_ = 0; // ERROR: inaccessible
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 585
Private Inheritance Example
1 class Base {
2 public:
3 void func_1();
4 protected:
5 void func_2();
6 private:
7 int x_;
8 };
9
10 class Derived : private Base {
11 public:
12 void func_3() {
13 func_1(); // OK
14 func_2(); // OK
15 x_ = 0; // ERROR: inaccessible
16 }
17 };
18
19 struct Widget : public Derived {
20 void func_4() { func_2(); } // ERROR: inaccessible
21 };
22
23 int main() {
24 Derived d; // OK: defaulted constructor is public
25 d.func_1(); // ERROR: inaccessible
26 d.func_2(); // ERROR: inaccessible
27 d.x_ = 0; // ERROR: inaccessible
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 586
Public Inheritance
■ public inheritance is inheritance in traditional object-oriented programming
sense
■ public inheritance models an is-a relationship (i.e., derived class object is
a base class object)
■ most common form of inheritance
■ inheritance relationship visible to all code
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 587
Public Inheritance Example
1 #include <string>
2
3 class Person {
4 public:
5 Person(const std::string& family_name, const std::string&
6 given_name) : family_name_(family_name),
7 given_name_(given_name) {}
8 std::string family_name() const
9 {return family_name_;}
10 std::string given_name() const
11 {return given_name_;}
12 std::string full_name() const
13 {return family_name_ + ", " + given_name_;}
14 private:
15 std::string family_name_;
16 std::string given_name_;
17 };
18
19 class Student : public Person {
20 public:
21 Student(const std::string& family_name, const std::string&
22 given_name, const std::string& student_id) :
23 Person(family_name, given_name), student_id_(student_id) {}
24 std::string student_id()
25 {return student_id_;}
26 private:
27 std::string student_id_;
28 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 588
Protected and Private Inheritance
■ protected and private inheritance not inheritance in traditional
object-oriented programming sense (i.e., no is-a relationship)
■ form of implementation inheritance
■ implemented-in-terms-of relationship (i.e., derived class object
implemented in terms of a base class object)
■ in case of protected inheritance, inheritance relationship only seen by
derived classes and their friends and class itself and its friends
■ in case of private inheritance, inheritance relationship only seen by class
itself and its friends (not derived classes and their friends)
■ except in special circumstances, normally bad idea to use inheritance for
composition
■ one good use case for private/protected inheritance is in policy-based
design, which exploits empty base optimization (EBO)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 589
Policy-Based Design Example: Inefficient Memory Usage
1 #include <mutex>
2
3 class ThreadSafePolicy {
4 public:
5 void lock() {mutex_.lock();}
6 void unlock() {mutex_.unlock();}
7 private:
8 std::mutex mutex_;
9 };
10
11 class ThreadUnsafePolicy {
12 public:
13 void lock() {} // no-op
14 void unlock() {} // no-op
15 };
16
17 template<class ThreadSafetyPolicy>
18 class Widget {
19 ThreadSafetyPolicy policy_;
20 // ...
21 };
22
23 int main() {
24 Widget<ThreadUnsafePolicy> w;
25 // w.policy_ has no data members, but
26 // sizeof(w.policy_) >= 1
27 // inefficient use of memory
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 590
Policy-Based Design Example: Private Inheritance and EBO
1 #include <mutex>
2
3 class ThreadSafePolicy {
4 public:
5 void lock() {mutex_.lock();}
6 void unlock() {mutex_.unlock();}
7 private:
8 std::mutex mutex_;
9 };
10
11 class ThreadUnsafePolicy {
12 public:
13 void lock() {} // no-op
14 void unlock() {} // no-op
15 };
16
17 template<class ThreadSafetyPolicy>
18 class Widget : ThreadSafetyPolicy {
19 // ...
20 };
21
22 int main() {
23 Widget<ThreadUnsafePolicy> w;
24 // empty-base optimization (EBO) can be applied
25 // no memory overhead for no-op thread-safety policy
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 591
Inheritance and Constructors
■ by default, constructors not inherited
■ often, derived class introduces new data members not in base class
■ since base-class constructors cannot initialize derived-class data
members, inheriting constructors from base class by default would be bad
idea (e.g., could lead to uninitialized data members)
■ in some cases, however, base-class constructors may be sufficient to
initialize derived-class objects
■ in such cases, can inherit all non-special base-class constructors with
using statement
■ special constructors (i.e., default, copy, and move constructors) cannot be
inherited ⁓⁓⁓⁓⁓⁓⁓⁓
[C++14 §12.9/3] [C++14 §12.9/6]
⁓⁓⁓⁓⁓⁓⁓⁓
■ constructors to be inherited with using statement may still be hidden by
constructors in derived class
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 592
Inheriting Constructors Example 1
1 class Base {
2 public:
3 Base() : i_(0.0), j_(0) {}
4 Base(int i) : i_(i), j_(0) {}
5 Base(int i, int j) : i_(i), j_(j) {}
6 // ... (other non-constructor members)
7 private:
8 int i_, j_;
9 };
10
11 class Derived : public Base {
12 public:
13 // inherit non-special constructors from Base
14 // (default constructor not inherited)
15 using Base::Base;
16 // default constructor is implicitly declared and
17 // not inherited
18 };
19
20 int main() {
21 Derived a;
22 // invokes non-inherited Derived::Derived()
23 Derived b(42, 42);
24 // invokes inherited Base::Base(int, int)
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 593
Inheriting Constructors Example 2
1 class Base {
2 public:
3 Base() : i_(0), j_(0), k_(0) {}
4 Base(int i, int j) : i_(i), j_(j), k_(0) {}
5 Base(int i, int j, int k) : i_(i), j_(j), k_(k) {}
6 // ... (other non-constructor members)
7 private:
8 int i_, j_, k_;
9 };
10
11 class Derived : public Base {
12 public:
13 // inherit non-special constructors from Base
14 // (default constructor not inherited)
15 using Base::Base;
16 // following constructor hides inherited constructor
17 Derived(int i, int j, int k) : Base(-i, -j, -k) {}
18 // no implicitly-generated default constructor
19 };
20
21 int main() {
22 Derived b(1, 2);
23 // invokes inherited Base::Base(int, int)
24 Derived c(1, 2, 3);
25 // invokes Derived::Derived(int, int, int)
26 // following would produce compile-time error:
27 // Derived a; // ERROR: no default constructor
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 594
Inheritance, Assignment Operators, and Destructors
■ by default, assignment operators not inherited (for similar reasons as in
case of constructors)
■ can inherit all non-special base-class assignment operators with using
statement
■ copy and move assignment operators cannot be inherited [C++14 §12.8/24]
⁓⁓⁓⁓⁓⁓⁓⁓
■ assignment operators to be inherited with using statement may still be
hidden by assignment operators in derived class
■ cannot inherit destructor
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 595
Inheriting Assignment Operators Example
1 class Base {
2 public:
3 explicit Base(int i) : i_(i) {}
4 Base& operator=(int i) {
5 i_ = i;
6 return *this;
7 }
8 // ...
9 private:
10 int i_;
11 };
12
13 class Derived : public Base {
14 public:
15 // inherit non-special constructors
16 using Base::Base;
17 // inherit non-special assignment operators
18 using Base::operator=;
19 // ...
20 };
21
22 int main() {
23 Derived d(0);
24 // invokes inherited Base::Base(int)
25 d = 42;
26 // invokes inherited Base::operator=(int)
27 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 596
Construction and Destruction Order
■ order of construction:
1 if most-derived class in hierarchy, initialize all virtual base class objects in
hierarchy in order of depth-first left-to-right traversal of graph of base class
declarations, where left to right refers to order of appearance of base class
names in class definition (virtual base classes to be discussed later)
2 initialize non-virtual (direct) base class objects in order listed in class
definition
3 initialize non-static data members in order of declaration in class definition
4 execute constructor body
■ order of destruction is exact reverse of order of construction, namely:
1 execute destructor body
2 destroy non-static data members in reverse of construction order
3 destroy non-virtual (direct) base class objects in reverse of construction
order
4 if most-derived class in hierarchy, destroy all virtual base class objects in
hierarchy in reverse of construction order
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 597
Order of Construction
1 #include <vector>
2 #include <string>
3
4 class Base {
5 public:
6 Base(int n) : v_(n, 0) {}
7 // ...
8 private:
9 std::vector<char> v_;
10 };
11
12 class Derived : public Base {
13 public:
14 Derived(const std::string& s) : Base(1024), s_(s)
15 { i_ = 0; }
16 // ...
17 private:
18 std::string s_;
19 int i_;
20 };
21
22 int main() {
23 Derived d("hello");
24 }
■ construction order for Derived constructor: 1) Base class object, 2) data
member s_, 3) Derived constructor body (initializes data member i_)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 598
Hiding Base-Class Member Functions in Derived Class
■ can provide new versions of member functions in derived class to hide
original functions in base class
1 #include <iostream>
2
3 class Fruit {
4 public:
5 void print() const {std::cout << "fruit\n";}
6 };
7
8 class Apple : public Fruit {
9 public:
10 void print() const {std::cout << "apple\n";}
11 };
12
13 class Banana : public Fruit {
14 public:
15 void print() const {std::cout << "banana\n";}
16 };
17
18 int main() {
19 Fruit f;
20 Apple a;
21 Banana b;
22 f.print(); // calls Fruit::print
23 a.print(); // calls Apple::print
24 b.print(); // calls Banana::print
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 599
Upcasting
■ derived-class object always has base-class subobject
■ given reference or pointer to derived-class object, may want to find
reference or pointer to corresponding base-class object
■ upcasting: converting derived-class pointer or reference to base-class
pointer or reference
■ upcasting allows us to treat derived-class object as base-class object
■ upcasting always safe in sense that cannot result in incorrect type (since
every derived-class object is also a base-class object)
■ can upcast without explicit type-cast operator as long as casted-to type is
accessible; C-style cast can used to bypass access protection (although
not recommended)
■ example:
class Base { /* ... */ };
class Derived : public Base { /* ... */ };
void func() {
Derived d;
Base* bp = &d;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 600
Downcasting
■ downcasting: converting base-class pointer or reference to derived-class
pointer or reference
■ downcasting allows us to force base-class object to be treated as
derived-class object
■ downcasting is not always safe (since not every base-class object is
necessarily also derived-class object)
■ must only downcast when known that object actually has derived type
(except in case of dynamic_cast)
■ downcasting always requires explicit cast (e.g., static_cast,
dynamic_cast for dynamically-checked cast in polymorphic case, or
C-style cast)
■ example:
class Base { /* ... (nonpolymorphic) */ };
class Derived : public Base { /* ... */ };
void func() {
Derived d;
Base* bp = &d;
Derived* dp = static_cast<Derived*>(bp);
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 601
Upcasting/Downcasting Example
1 class Base { /* ... (nonpolymorphic) */ };
2
3 class Derived : public Base { /* ... */ };
4
5 int main() {
6 Base b;
7 Derived d;
8 Base* bp = nullptr;
9 Derived* dp = nullptr;
10 bp = &d;
11 // OK: upcast does not require explicit cast
12 dp = bp;
13 // ERROR: downcast requires explicit cast
14 dp = static_cast<Derived*>(bp);
15 // OK: downcast with explicit cast and
16 // pointer (bp) refers to Derived object
17 Base& br = d;
18 // OK: upcast does not require explicit cast
19 Derived& dr1 = *bp;
20 // ERROR: downcast requires explicit cast
21 Derived& dr2 = *static_cast<Derived*>(bp);
22 // OK: downcast with explicit cast and
23 // object (*bp) is of Derived type
24 dp = static_cast<Derived*>(&b);
25 // BUG: pointer (&b) does not refer to Derived object
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 602
Upcasting Example
1 class Base { /* ... */ };
2
3 class Derived : public Base { /* ... */ };
4
5 void func_1(Base& b) { /* ... */ }
6
7 void func_2(Base* b) { /* ... */ }
8
9 int main() {
10 Base b;
11 Derived d;
12 func_1(b);
13 func_1(d); // OK: Derived& upcast to Base&
14 func_2(&b);
15 func_2(&d); // OK: Derived* upcast to Base*
16 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 603
Nonpolymorphic Behavior
1 #include <iostream>
2 #include <string>
3
4 class Person {
5 public:
6 Person(const std::string& family, const std::string& given) :
7 family_(family), given_(given) {}
8 void print() const {std::cout << "person: " << family_ << ’,’ << given_ << ’\n’;}
9 protected:
10 std::string family_; // family name
11 std::string given_; // given name
12 };
13
14 class Student : public Person {
15 public:
16 Student(const std::string& family, const std::string& given,
17 const std::string& id) : Person(family, given), id_(id) {}
18 void print() const {
19 std::cout << "student: " << family_ << ’,’ << given_ << ’,’ << id_ << ’\n’;
20 }
21 private:
22 std::string id_; // student ID
23 };
24
25 void processPerson(const Person& p) {
26 p.print(); // always calls Person::print
27 // ...
28 }
29
30 int main() {
31 Person p("Ritchie", "Dennis");
32 Student s("Doe", "John", "12345678");
33 processPerson(p); // invokes Person::print
34 processPerson(s); // invokes Person::print
35 }
■ would be nice if processPerson called version of print that corresponds
to actual type of object referenced by function parameter p
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 604
Slicing
■ slicing: copying or moving object of derived class to object of base class
(e.g., during construction or assignment), losing part of information in so
doing
■ example:
1 class Base {
2 // ...
3 int x_;
4 };
5
6 class Derived : public Base {
7 // ...
8 int y_;
9 };
10
11 int main() {
12 Derived d1, d2;
13 Base b = d1;
14 // slicing occurs
15 Base& r = d1;
16 r = d2;
17 // more treacherous case of slicing
18 // slicing occurs
19 // d1 now contains mixture of d1 and d2
20 // (i.e., base part of d2 and derived part of d1)
21 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 605
Inheritance and Overloading
■ functions do not overload across scopes
■ can employ using statement to bring base members into scope for
overloading
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 606
Inheritance and Overloading Example
1 #include <iostream>
2
3 class Base {
4 public:
5 double f(double d) const {return d;}
6 // ...
7 };
8
9 class Derived : public Base {
10 public:
11 int f(int i) const {return i;}
12 // ...
13 };
14
15 int main()
16 {
17 Derived d;
18 std::cout << d.f(0) << ’\n’;
19 // calls Derived::f(int) const
20 std::cout << d.f(0.5) << ’\n’;
21 // calls Derived::f(int) const; probably not intended
22 Derived* dp = &d;
23 std::cout << dp->f(0) << ’\n’;
24 // calls Derived::f(int) const
25 std::cout << dp->f(0.5) << ’\n’;
26 // calls Derived::f(int) const; probably not intended
27 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 607
Using Base Members Example
1 #include <iostream>
2
3 class Base {
4 public:
5 double f(double d) const {return d;}
6 // ...
7 };
8
9 class Derived : public Base {
10 public:
11 using Base::f; // bring Base::f into scope
12 int f(int i) const {return i;}
13 // ...
14 };
15
16 int main()
17 {
18 Derived d;
19 std::cout << d.f(0) << ’\n’;
20 // calls Derived::f(int) const
21 std::cout << d.f(0.5) << ’\n’;
22 // calls Base::f(double) const
23 Derived* dp = &d;
24 std::cout << dp->f(0) << ’\n’;
25 // calls Derived::f(int) const
26 std::cout << dp->f(0.5) << ’\n’;
27 // calls Base::f(double) const
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 608
Inheritance, Templates, and Name Lookup
■ name lookup in templates takes place in two phases:
1 at template definition time
2 at template instantiation time
■ at template definition time, compiler parses template and looks up any
nondependent names
■ result of nondependent name lookup must be identical in all instantiations
of template (since, by definition, nondependent name does not depend on
template parameter)
■ at template instantiation time, compiler looks up any dependent names
■ results of dependent name lookup can differ from one template
instantiation to another (since, by definition, dependent name depends on
template parameters)
■ two-phase name lookup can interact with inheritance in ways that can
sometimes lead to unexpected problems in code
■ may need to add “this->” or employ using statement to make name
dependent (when it would otherwise be nondependent)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 609
Name Lookup Example (Incorrect Code)
1 #include <iostream>
2
3 template <class T>
4 struct Base {
5 using Real = T;
6 Base(Real x_ = Real()) : x(x_) {}
7 void f() {std::cout << x << "\n";};
8 Real x;
9 };
10
11 template <class T>
12 struct Derived : Base<T> {
13 Derived(Real y_ = Real()) : y(y_) {}
14 // ERROR: Real (which is nondependent and looked up at
15 // template definition time) is assumed to be defined
16 // outside class
17 void g() {
18 x = y;
19 // ERROR: x assumed to be object outside class
20 f();
21 // ERROR: f assumed to be function outside class
22 }
23 Real y;
24 };
25
26 int main() {
27 Derived<double> w(0.0);
28 w.g();
29 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 610
Name Lookup Example (Correct Code)
1 #include <iostream>
2
3 template <class T>
4 struct Base {
5 using Real = T;
6 Base(Real x_ = Real()) : x(x_) {}
7 void f() {std::cout << x << "\n";};
8 Real x;
9 };
10
11 template <class T>
12 struct Derived : Base<T> {
13 using Real = typename Base<T>::Real;
14 // OK: Base<T>::Real dependent
15 Derived(Real y_ = Real()) : y(y_) {}
16 void g() {
17 this->x = y; // OK: this->x dependent
18 this->f(); // OK: this->f dependent
19 }
20 Real y;
21 };
22
23 int main() {
24 Derived<double> w(0.0);
25 w.g();
26 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 611
Section 2.9.2
Virtual Functions and Run-Time Polymorphism
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 612
Run-Time Polymorphism
■ polymorphism is characteristic of being able to assign different meaning
to something in different contexts
■ polymorphism that occurs at run time called run-time polymorphism
(also known as dynamic polymorphism)
■ in context of inheritance, key type of run-time polymorphism is
polymorphic function call (also known as dynamic dispatch)
■ when inheritance relationship exists between two classes, type of
reference or pointer to object may not correspond to actual dynamic (i.e.,
run-time) type of object referenced by reference or pointer
■ that is, reference or pointer to type T may, in fact, refer to object of type D,
where D is either directly or indirectly derived from T
■ when calling member function through pointer or reference, may want
actual function invoked to be determined by dynamic type of object
referenced by pointer or reference
■ function call with this property said to be polymorphic
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 613
Virtual Functions
■ in context of class hierarchies, polymorphic function calls achieved
through use of virtual functions
■ virtual function is member function with polymorphic behavior
■ when call made to virtual function through reference or pointer, actual
function invoked will be determined by dynamic type of referenced object
■ to make member function virtual, add keyword virtual to function
declaration
■ example:
class Base {
public:
virtual void func(); // virtual function
// ...
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 614
Virtual Functions (Continued)
■ once function made virtual, it will automatically be virtual in all derived
classes, regardless of whether virtual keyword is used in derived
classes
■ therefore, not necessary to repeat virtual qualifier in derived classes
(and perhaps preferable not to do so)
■ virtual function must be defined in class where first declared unless pure
virtual function (to be discussed shortly)
■ derived class inherits definition of each virtual function from its base class,
but may override each virtual function with new definition
■ function in derived class with same name and same set of argument types
as virtual function in base class overrides base class version of virtual
function
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 615
Virtual Function Example
1 #include <iostream>
2 #include <string>
3
4 class Person {
5 public:
6 Person(const std::string& family, const std::string& given) :
7 family_(family), given_(given) {}
8 virtual void print() const
9 {std::cout << "person: " << family_ << ’,’ << given_ << ’\n’;}
10 protected:
11 std::string family_; // family name
12 std::string given_; // given name
13 };
14
15 class Student : public Person {
16 public:
17 Student(const std::string& family, const std::string& given,
18 const std::string& id) : Person(family, given), id_(id) {}
19 void print() const {
20 std::cout << "student: " << family_ << ’,’ << given_ << ’,’ << id_ << ’\n’;
21 }
22 private:
23 std::string id_; // student ID
24 };
25
26 void processPerson(const Person& p) {
27 p.print(); // polymorphic function call
28 // ...
29 }
30
31 int main() {
32 Person p("Ritchie", "Dennis");
33 Student s("Doe", "John", "12345678");
34 processPerson(p); // invokes Person::print
35 processPerson(s); // invokes Student::print
36 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 616
Override Control: The override Qualifier
■ when looking at code for derived class, often not possible to determine if
member function intended to override virtual function in base class (or one
of its base classes)
■ can sometimes lead to bugs where programmer expects member function
to override virtual function when function not virtual
■ override qualifier used to indicate that member function is expected to
override virtual function in parent class; must come at end of function
declaration
■ example:
class Person {
public:
virtual void print() const;
// ...
};
class Employee : public Person {
public:
void print() const override; // must be virtual
// ...
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 617
Override Control: The final Qualifier
■ sometimes, may want to prevent any further overriding of virtual function
in any subsequent derived classes
■ adding final qualifier to declaration of virtual function prevents function
from being overridden in any subsequent derived classes
■ preventing further overriding can sometimes allow for better optimization
by compiler (e.g., via devirtualization)
■ example:
class A {
public:
virtual void doStuff();
// ...
};
class B : public A {
public:
void doStuff() final; // prevent further overriding
// ...
};
class C : public B {
public:
void doStuff(); // ERROR: cannot override
// ...
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 618
final Qualifier Example
1 class Worker {
2 public:
3 virtual void prepareEnvelope();
4 // ...
5 };
6
7 class SpecialWorker : public Worker {
8 public:
9 // prevent overriding function responsible for
10 // overall envelope preparation process
11 // but allow functions for individual steps in
12 // process to be overridden
13 void prepareEnvelope() final {
14 stuffEnvelope(); // step 1
15 lickEnvelope(); // step 2
16 sealEnvelope(); // step 3
17 }
18 virtual void stuffEnvelope();
19 virtual void lickEnvelope();
20 virtual void sealEnvelope();
21 // ...
22 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 619
Constructors, Destructors, and Virtual Functions
■ except in very rare cases, destructors in class hierarchy need to be virtual
■ otherwise, invoking destructor through base-class pointer/reference would
only destroy base-class part of object, leaving remainder of derived-class
object untouched
■ normally, bad idea to call virtual function inside constructor or destructor
■ dynamic type of object changes during construction and changes again
during destruction
■ final overrider of virtual function will change depending where in hierarchy
virtual function call is made
■ when constructor/destructor being executed, object is of exactly that type,
never type derived from it
■ although semantics of virtual function calls during construction and
destruction well defined, easy to write code where actual overrider not
what expected (and might even be pure virtual)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 620
Problematic Code with Non-Virtual Destructor
1 class Base {
2 public:
3 Base() {}
4 ~Base() {} // non-virtual destructor
5 // ...
6 };
7
8 class Derived : public Base {
9 public:
10 Derived() : buffer_(new char[10’000]) {}
11 ~Derived() {delete[] buffer_;}
12 // ...
13 private:
14 char* buffer_;
15 };
16
17 void process(Base* bp) {
18 // ...
19 delete bp; // always invokes only Base::~Base
20 }
21
22 int main() {
23 process(new Base);
24 process(new Derived); // leaks memory
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 621
Corrected Code with Virtual Destructor
1 class Base {
2 public:
3 Base() {}
4 virtual ~Base() {} // virtual destructor
5 // ...
6 };
7
8 class Derived : public Base {
9 public:
10 Derived() : buffer_(new char[10’000]) {}
11 ~Derived() {delete[] buffer_;}
12 // ...
13 private:
14 char* buffer_;
15 };
16
17 void process(Base* bp) {
18 // ...
19 delete bp; // invokes destructor polymorphically
20 }
21
22 int main() {
23 process(new Base);
24 process(new Derived);
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 622
Preventing Creation of Derived Classes
■ in some situations, may want to prevent deriving from class
■ language provides means for accomplishing this
■ in class/struct declaration, after name of class can add keyword final to
prevent deriving from class
■ example:
class Widget final { /* ... */ };
class Gadget : public Widget { /* ... */ };
// ERROR: cannot derive from Widget
■ might want to prevent deriving from class with destructor that is not virtual
■ preventing derivation can sometimes also facilitate better compiler
optimization (e.g., via devirtualization)
■ might want to prevent derivation so that objects can be copied safely
without fear of slicing
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 623
Covariant Return Type
■ in some special cases, language allows relaxation of rule that type of
overriding function f must be same as type of virtual function f overrides
■ in particular, requirement that return type be same is relaxed
■ return type of derived-class function is permitted to be type derived
(directly or indirectly) from return type of base-class function
■ this relaxation of return type more formally known as covariant return
type
■ case of pointer return type: if original return type B*, return type of
overriding function may be D*, provided B is public base of D (i.e., may
return pointer to more derived type)
■ case of reference return type: if original return type B& (or B&&), return
type of overriding function may be D& (or D&&), provided B is public base of
D (i.e., may return reference to more derived type)
■ covariant return type can sometimes be exploited in order to avoid need
for type casts
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 624
Covariant Return Type Example: Cloning
1 class Base {
2 public:
3 virtual Base* clone() const {
4 return new Base(*this);
5 }
6 // ...
7 };
8
9 class Derived : public Base {
10 public:
11 // use covariant return type
12 Derived* clone() const override {
13 return new Derived(*this);
14 }
15 // ...
16 };
17
18 int main() {
19 Derived* d = new Derived;
20 Derived* d2 = d->clone();
21 // OK: return type is Derived*
22 // without covariant return type, would need cast:
23 // Derived* d2 = static_cast<Derived*>(d->clone());
24 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 625
Pure Virtual Functions
■ sometimes desirable to require derived class to override virtual function
■ pure virtual function: virtual function that must be overridden in every
derived class
■ to declare virtual function as pure, add “= 0” at end of declaration
■ example:
class Widget {
public:
virtual void doStuff() = 0; // pure virtual
// ...
};
■ pure virtual function can still be defined, although likely only useful in case
of virtual destructor
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 626
Abstract Classes
■ class with one or more pure virtual functions called abstract class
■ cannot directly instantiate objects of abstract class (can only use them as
base class objects)
■ class that derives from abstract class need not override all of its pure
virtual methods
■ class that does not override all pure virtual methods of abstract base class
will also be abstract
■ most commonly, abstract classes have no state (i.e., data members) and
used to provide interfaces, which can be inherited by other classes
■ if class has no pure virtual functions and abstract class is desired, can
make destructor pure virtual (but must provide definition of destructor
since invoked by derived classes)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 627
Abstract Class Example
1 #include <cmath>
2
3 class Shape {
4 public:
5 virtual bool isPolygon() const = 0;
6 virtual float area() const = 0;
7 virtual ~Shape() {};
8 };
9
10 class Rectangle : public Shape {
11 public:
12 Rectangle(float w, float h) : w_(w), h_(h) {}
13 bool isPolygon() const override {return true;}
14 float area() const override {return w_ * h_;}
15 private:
16 float w_; // width of rectangle
17 float h_; // height of rectangle
18 };
19
20 class Circle : public Shape {
21 public:
22 Circle(float r) : r_(r) {}
23 float area() const override {return M_PI * r_ * r_;}
24 bool isPolygon() const override {return false;}
25 private:
26 float r_; // radius of circle
27 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 628
Pure Virtual Destructor Example
1 class Abstract {
2 public:
3 virtual ~Abstract() = 0; // pure virtual destructor
4 // ... (no other virtual functions)
5 };
6
7 inline Abstract::~Abstract()
8 { /* possibly empty */ }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 629
The dynamic_cast Operator
■ often need to upcast and downcast (as well as cast sideways) in
inheritance hierarchy
■ dynamic_cast can be used to safely perform type conversions on
pointers and references to classes
■ syntax: dynamic_cast<T>(expr)
■ types involved must be polymorphic (i.e., have at least one virtual
function)
■ inspects run-time information about types to determine whether cast can
be safely performed
■ if conversion is valid (i.e., expr can validly be cast to T), casts expr to type
T and returns result
■ if conversion is not valid, cast fails
■ if expr is of pointer type, nullptr is returned upon failure
■ if expr is of reference type, std::bad_cast exception is thrown upon
failure (where exceptions are discussed later)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 630
dynamic_cast Example
1 #include <cassert>
2
3 class Base {
4 public:
5 virtual void doStuff() { /* ... */ };
6 // ...
7 };
8
9 class Derived1 : public Base { /* ... */ };
10 class Derived2 : public Base { /* ... */ };
11
12 bool isDerived1(Base& b) {
13 return dynamic_cast<Derived1*>(&b) != nullptr;
14 }
15
16 int main() {
17 Base b;
18 Derived1 d1;
19 Derived2 d2;
20 assert(isDerived1(b) == false);
21 assert(isDerived1(d1) == true);
22 assert(isDerived1(d2) == false);
23 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 631
Cost of Run-Time Polymorphism
■ typically, run-time polymorphism does not come without run-time cost in
terms of both time and memory
■ in some contexts, cost can be significant
■ typically, virtual functions implemented using virtual function table
■ each polymorphic class has virtual function table containing pointers to all
virtual functions for class
■ each polymorphic class object has pointer to virtual function table
■ memory cost to store virtual function table and pointer to table in each
polymorphic object
■ in most cases, impossible for compiler to inline virtual function calls since
function to be called cannot be known until run time
■ each virtual function call is made through pointer, which adds overhead
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 632
Curiously-Recurring Template Pattern (CRTP)
■ when derived type known at compile time, may want behavior similar to
virtual functions but without run-time cost (by performing binding at
compile time instead of run time)
■ can be achieved with technique known as curiously-recurring template
pattern (CRTP)
■ class Derived derives from class template instantiation using Derived
itself as template argument
■ example:
template <class Derived>
class Base {
// ...
};
class Derived : public Base<Derived> {
// ...
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 633
CRTP Example: Static Polymorphism
1 #include <iostream>
2
3 template <class Derived>
4 class Base {
5 public:
6 void interface() {
7 std::cout << "Base::interface called\n";
8 static_cast<Derived*>(this)->implementation();
9 }
10 // ...
11 };
12
13 class Derived : public Base<Derived> {
14 public:
15 void implementation() {
16 std::cout << "Derived::implementation called\n";
17 }
18 // ...
19 };
20
21 int main() {
22 Derived d;
23 d.interface();
24 // calls Base::interface which, in turn, calls
25 // Derived::implementation
26 // no virtual function call, however
27
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 634
CRTP Example: Static Polymorphism
1 class TreeNode {
2 public:
3 enum Kind {RED, BLACK}; // kinds of nodes
4 TreeNode *left(); // get left child node
5 TreeNode *right(); // get right child node
6 Kind kind(); // get kind of node
7 // ...
8 };
9
10 template <class Derived>
11 class GenericVisitor {
12 public:
13 void visit_preorder(TreeNode* node) {
14 if (node) {
15 process_node(node);
16 visit_preorder(node->left());
17 visit_preorder(node->right());
18 }
19 }
20 void visit_inorder(TreeNode* node) { /* ... */ }
21 void visit_postorder(TreeNode* node) { /* ... */ }
22 void process_red_node(TreeNode* node) { /* ... */ };
23 void process_black_node(TreeNode* node) { /* ... */ };
24 private:
25 Derived& derived() {return *static_cast<Derived*>(this);}
26 void process_node(TreeNode* node) {
27 if (node->kind() == TreeNode::RED) {
28 derived().process_red_node(node);
29 } else {
30 derived().process_black_node(node);
31 }
32 }
33 };
34
35 class SpecialVisitor : public GenericVisitor<SpecialVisitor> {
36 public:
37 void process_red_node(TreeNode* node) { /* ... */ }
38 };
39
40 int main() {SpecialVisitor v;}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 635
CRTP Example: Comparisons
1 #include <cassert>
2
3 template<class Derived>
4 struct Comparisons {
5 friend bool operator==(const Comparisons<Derived>& x,
6 const Comparisons<Derived>& y) {
7 const Derived& xr = static_cast<const Derived&>(x);
8 const Derived& yr = static_cast<const Derived&>(y);
9 return !(xr < yr) && !(yr < xr);
10 }
11 // operator!= and others
12 };
13
14 class Widget : public Comparisons<Widget> {
15 public:
16 Widget(bool b, int i) : b_(b), i_(i) {}
17 friend bool operator<(const Widget& x, const Widget& y)
18 {return x.i_ < y.i_;}
19 private:
20 bool b_;
21 int i_;
22 };
23
24 int main() {
25 Widget w1(true, 1);
26 Widget w2(false, 1);
27 assert(w1 == w2);
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 636
CRTP Example: Object Counting
1 #include <iostream>
2 #include <cstdlib>
3
4 template <class T>
5 class Counter {
6 public:
7 Counter() {++count_;}
8 Counter(const Counter&) {++count_;}
9 ~Counter() {--count_;}
10 static std::size_t howMany() {return count_;}
11 private:
12 static std::size_t count_;
13 };
14
15 template <class T>
16 std::size_t Counter<T>::count_ = 0;
17
18 // inherit from Counter to count objects
19 class Widget: private Counter<Widget> {
20 public:
21 using Counter<Widget>::howMany;
22 // ...
23 };
24
25 int main() {
26 Widget w1; int c1 = Widget::howMany();
27 Widget w2, w3; int c2 = Widget::howMany();
28 std::cout << c1 << ’ ’ << c2 << ’\n’;
29 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 637
Section 2.9.3
Multiple Inheritance and Virtual Inheritance
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 638
Multiple Inheritance
■ language allows derived class to inherit from more than one base class
■ multiple inheritance (MI): deriving from more than one base class
■ although multiple inheritance not best solution for most problems, does
have some compelling use cases
■ one compelling use case is for inheriting interfaces by deriving from
abstract base classes with no data members
■ when misused, multiple inheritance can lead to very convoluted code
■ in multiple inheritance contexts, ambiguities in naming can arise
■ for example, if class Derived inherits from classes Base1 and Base2,
each of which have member called x, name x can be ambiguous in some
contexts
■ scope resolution operator can be used to resolve ambiguous names
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 639
Ambiguity Resolution Example
1 class Base1 {
2 public:
3 void func();
4 // ...
5 };
6
7 class Base2 {
8 void func();
9 // ...
10 };
11
12 class Derived : public Base1, public Base2 {
13 public:
14 // ...
15 };
16
17 int main() {
18 Derived d;
19 d.func(); // ERROR: ambiguous function call
20 d.Base1::func(); // OK: invokes Base1::func
21 d.Base2::func(); // OK: invokes Base2::func
22 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 640
Multiple Inheritance Example
1 class Input_stream {
2 public:
3 virtual ~Input_stream() {}
4 virtual int read_char() = 0;
5 virtual int read(char* buffer, int size) = 0;
6 virtual bool is_input_ready() const = 0;
7 // ...(all pure virtual, no data)
8 };
9
10 class Output_stream {
11 public:
12 virtual ~Output_stream() {}
13 virtual int write_char(char c) = 0;
14 virtual int write(char* buffer, int size) = 0;
15 virtual int flush_output() = 0;
16 // ... (all pure virtual, no data)
17 };
18
19 class Input_output_stream : public Input_stream,
20 public Output_stream {
21 // ...
22 };
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 641
Dreaded Diamond Inheritance Pattern
■ use of multiple inheritance can lead to so called dreaded diamond
scenario
■ dreaded diamond inheritance pattern has following form:
A
B C
■ class D will have two subobjects of class A, since class D (indirectly)
inherits twice from class A
■ situation like one above probably undesirable and often sign of poor
design
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 642
Dreaded Diamond Example
1 class Base {
2 public:
3 // ...
4 protected:
5 int data_;
6 };
7
8 class D1 : public Base { /* ... */ };
9
10 class D2 : public Base { /* ... */ };
11
12 class Join : public D1, public D2 {
13 public:
14 void method() {
15 data_ = 1; // ERROR: ambiguous
16 D1::data_ = 1; // OK: unambiguous
17 }
18 };
19
20 int main() {
21 Join* j = new Join();
22 Base* b;
23 b = j; // ERROR: ambiguous
24 b = static_cast<D1*>(j); // OK: unambiguous
25 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 643
Virtual Inheritance
■ when using multiple inheritance, may want to ensure that only one
instance of base-class object can appear in derived-class object
■ virtual base class: base class that is only ever included once in derived
class, even if derived from multiple times
■ virtual inheritance: when derived class inherits from base class that is
virtual
■ virtual inheritance can be used to avoid situations like dreaded diamond
pattern
■ order of construction: virtual base classes constructed first in depth-first
left-to-right traversal of graph of base classes, where left-to-right refers to
order of appearance of base class names in class definition
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 644
Avoiding Dreaded Diamond With Virtual Inheritance
1 class Base {
2 public:
3 // ...
4 protected:
5 int data_;
6 };
7
8 class D1 : public virtual Base { /* ... */ };
9
10 class D2 : public virtual Base { /* ... */ };
11
12 class Join : public D1, public D2 {
13 public:
14 void method() {
15 data_ = 1; // OK: unambiguous
16 }
17 };
18
19 int main() {
20 Join* j = new Join();
21 Base* b = j; // OK: unambiguous
22 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 645
Section 2.9.4
References
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 646
References I
1 N. Meyers. The empty base C++ optimization. Dr. Dobb’s Journal, Aug.
1997. Available online at http://www.cantrip.org/emptyopt.html.
2 J. O. Coplien. Curiously recurring template patterns. C++ Report, pages
24–27, Feb. 1995.
3 S. Meyers. Counting objects in C++. C++ User’s Journal, Apr. 1998.
Available online at http:
//www.drdobbs.com/cpp/counting-objects-in-c/184403484.
4 A. Nasonov. Better encapsulation for the curiously recurring template
pattern. Overload, 70:11–13, Dec. 2005.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 647
Section 2.10
Modules
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 648
Modules
■ modules are language facility that allow better organization of code
■ modules provide encapsulation and isolation (from macros and
declarations)
■ likelihood of certain types of bugs can be reduced by improved code
organization that modules allow
■ by using modules, can reduce amount of redundant work needed during
compilation
■ use of modules fundamentally changes how code is built
■ build process significantly more complicated due to additional
dependencies introduced by modules
■ therefore, build system support for modules is critically important
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 649
Section 2.10.1
Compilation Model
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 650
Header Model for Compilation
■ header model of compilation is that of textual inclusion
■ software component partitioned into interface and implementation parts
■ interface part placed in header
■ implementation part placed in regular (i.e., non-header) source files
■ no construct in language logically groups interface and implementation
■ in order to use software component, must include its corresponding
header
■ preprocessing stage of compilation replaces each include directive with
contents of included header
■ this substitution performed recursively so that include directive for header
replaced with all contents of all headers directly or indirectly included by
that header
■ from compiler’s point of view, no distinction made between code from
header and code that includes header
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 651
Legacy Compilation
Source File
Headers Included
Directly or Indirectly Compile Object File
by Source File
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 652
Example: Header Model for Compilation
Programmer’s View
a.hpp
// A-1
#include "b.hpp" Compiler’s View
// A-2
Translation Unit To Be Compiled
b.hpp // A-1
// B
// B // A-2
int main() {
// ...
main.hpp }
#include "a.hpp"
int main() {
// ...
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 653
Advantages of Header Model
■ do not need to compile code in headers as separate entities before
compiling source file that includes those headers
■ so source files can be compiled with no ordering constraints (except when
some source/headers generated as part of build process)
■ consequently, build process is very simple and highly parallelizable
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 654
Header Model and Redundant Compilation Work
util_1.cpp
#include <vector>
// ... (code that uses std::vector)
util_2.cpp
#include <vector>
// ... (code that uses std::vector)
main.cpp
#include <vector>
int main() {
// ... code that uses std::vector
}
■ when multiple source files include same header, this leads to repeated compilation
of same code
■ building program comprised of above three source files requires compiling code
included from vector header three times
■ furthermore, amount of code added by inclusion of vector header not small (e.g.,
on order of tens of thousands of lines of code)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 655
Amount of Code Included By System Headers
■ amount of code included (either directly or indirectly) by system headers
often quite significant
■ so repeatedly compiling code included from system headers can entail
substantial amount of extra work
■ for each of several system headers, number of lines of code included by
source file with single include directive for header as follows:
Lines of Code Included
GCC 10.2.0 Clang 11.0.0
Header libstdc++ libc++
iosfwd 1289 898
map 24095 36011
vector 27584 35095
string 34092 36767
iostream 43068 49790
filesystem 59125 56543
algorithm 62306 30127
regex 99642 53426
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 656
Header Model and One-Definition Rule (ODR) Violations
logger.hpp logger.cpp
1 #include <string> 1 #include "logger.hpp"
2 #include <iostream> 2 void logger::message(
3 class logger { 3 const std::string& s) {
4 public: 4 *out_ << s << ’\n’;
5 logger(std::ostream& out) : 5 #ifdef ENABLE_STATISTICS
6 out_(&out) {} 6 ++count_;
7 void message( 7 #endif
8 const std::string& s); 8 }
9 private:
10 #ifdef ENABLE_STATISTICS main.cpp
11 std::size_t get_count() const
12 {return count_;} 1 #include "logger.hpp"
13 std::size_t count_; 2 int main() {
14 #endif 3 logger log(std::cout);
15 std::ostream* out_; 4 log.message("hello");
16 }; 5 }
■ repeated compilation of code in header can be performed in inconsistent
manner, leading to one-definition rule (ODR) violations
■ consider what happens if logger.cpp and main.cpp compiled with
ENABLE_STATISTICS not consistently defined
■ with header model mistake like this easy to make, since code in header
potentially compiled more than once
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 657
Header Model and Poor Encapsulation
hg2g.hpp
1 #ifndef HG2G_HPP app.cpp
2 #define HG2G_HPP
3 #ifdef assert 1 #include <cassert>
4 #undef assert 2 #include "hg2g.hpp"
5 #endif 3
6 #define assert(x) /* Don’t panic! */ 4 int main() {
7 namespace hg2g { 5 auto i = hg2g::get_answer(); // OK
8 namespace detail { 6 auto j = hg2g::detail::helper();
9 using c42_t = char[42]; 7 // BAD: access to implementation
10 int helper() 8 // detail is possible
11 {return sizeof(c42_t);}; 9 assert(i == 42);
12 } 10 // BAD: assert is noop
13 int get_answer() 11 return i == 42 ? 0 : 1;
14 {return detail::helper();} 12 }
15 }
16 #endif
■ all declarations in header visible to includer, which can often expose many
implementation details
■ header can define, undefine, or redefine macros in manner that includer
does not expect (and vice versa), resulting in incorrect code behavior
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 658
Header Model and Ordering Dependencies
util.hpp
1 #ifndef UTIL_HPP
2 #define UTIL_HPP
3 #ifdef assert
4 # undef assert
5 #endif
6 #include <iostream>
7 #define assert(x) ((x) || std::cerr << "Panic!" << std::endl)
8 // ... more code
9 #endif
app.cpp
1 #include "util.hpp"
2 #include <cassert>
3
4 int main() {
5 assert(false);
6 // BAD: behavior depends on order of include directives above
7 }
■ changing order of include directives in app.cpp alters code behavior (i.e.,
prints "Panic!\n" instead of aborting when NDEBUG not defined)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 659
Disadvantages of Header Model
■ slow compilation resulting from repeated compilation of headers
2 header files must be be parsed each time included
■ increased risk of one-definition rule (ODR) violations
2 only allowed one definition (e.g., of variables, functions, and templates) per
translation unit and only allowed one definition of non-inline functions and
variables in entire program
2 headers greatly increase danger of multiple inconsistent definitions
■ lack of encapsulation/isolation
2 aside from private members, all included entities are accessible (i.e.,
everything in header unavoidably becomes part of interface)
2 inclusion of header can unexpectedly change meaning of code (e.g., by
defining macros or other entities not expected to define)
■ ordering dependencies and cyclic dependencies
2 order in which headers are included can affect semantics of source code
■ header/source split
2 often need to duplicate declarations in both header and (non-header)
source files, while careful to ensure multiple declarations are consistent
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 660
Module Model for Compilation
■ interface and implementation parts of software component logically
grouped into module, which is language construct
■ module consists of one or more translation units
■ two basic flavors of module translation units:
2 interface unit, which specifies interface part of software component
2 implementation unit, which provides implementation of interface
■ unlike header, module is independently compilable entity (i.e., translation
unit that uses module is distinct from translation units that comprise
module)
■ module interface units precompiled into compiled-module interfaces
(CMIs)
■ module interface units may also need to be compiled to produce object
files
■ in order to use module, translation unit must import module
■ before compiling translation unit, must first precompile interface units for
all modules (directly or indirectly) imported by translation unit
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 661
Compilation (With Modules)
Source File Is Not Module Interface Unit
Source File
Headers Included
Compile Object File
By Source File
CMIs for Modules and Header Units
Imported by Source File
Source File Is Module Interface Unit
Source File
Object File
Headers Included
Compile (If Needed in Addition
By Source File
to Output CMI)
CMIs for Modules and Header Units
Output CMI for Module Interface
Imported by Source File
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 662
Greet Example: Without and With Modules
Without Modules
With Modules
greetings.hpp (Interface) greetings-m.cpp (Module Interface Unit)
1 #ifndef GREETINGS_HPP
1 export module greetings;
2 #define GREETINGS_HPP
2 import <string>;
3 #include <string> 3
4
4 namespace greetings {
5 namespace greetings {
5 export std::string get_greeting();
6 std::string get_greeting();
6 }
7 }
8 #endif
greetings.cpp (Module Implementation Unit)
greetings.cpp (Implementation) 1 module greetings;
2 import <string>;
1 #include "greetings.hpp" 3
2
4 namespace greetings {
3 namespace greetings { 5 std::string get_greeting()
4 std::string get_greeting() 6 {return "Hello, World!";}
5 {return "Hello, World!";} 7 }
6 }
greet.cpp (Application) greet.cpp (Application)
1 import <iostream>;
1 #include <iostream>
2 import greetings;
2 #include "greetings.hpp" 3
3
4 int main() {
4 int main() { 5 std::cout << greetings::get_greeting()
5 std::cout << greetings::get_greeting() 6 << std::endl;
6 << std::endl; 7 return !std::cout;
7 return !std::cout; 8 }
8 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 663
Greet Example: Building With GCC
Without Modules
1 # 1. compile all source files in any order
2 g++ -std=c++20 -c greetings.cpp
3 g++ -std=c++20 -c greet.cpp
4 # 2. link
5 g++ -std=c++20 -o greet greet.o greetings.o
With Modules
1 # 0. set base compiler flags
2 cxxflags="-std=c++20 -fmodules-ts"
3 # 1. generate system header units
4 g++ $cxxflags -x c++-system-header -c iostream
5 g++ $cxxflags -x c++-system-header -c string
6 # 2. generate CMI and object files for module interface units
7 # in dependency order
8 g++ $cxxflags -c greetings-m.cpp
9 # 3. compile remaining source files in any order
10 g++ $cxxflags -c greetings.cpp
11 g++ $cxxflags -c greet.cpp
12 # 4. link
13 g++ $cxxflags -o greet greet.o greetings-m.o greetings.o
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 664
Greet Example: Building With Clang
Without Modules
1 # 1. compile all source files in any order
2 clang++ -std=c++20 -c greetings.cpp
3 clang++ -std=c++20 -c greet.cpp
4 # 2. link
5 clang++ -std=c++20 -o greet greet.o greetings.o
With Modules
1 # 0. set base compiler flags
2 cxxflags="-std=c++20 -fmodules -stdlib=libc++ -fprebuilt-module-path=."
3 # 1. generate CMIs for module interface units in dependency order
4 clang++ $cxxflags --precompile -x c++-module -o greetings.pcm \
5 -c greetings-m.cpp
6 # 2. compile remaining source files in any order
7 clang++ $cxxflags -c greetings-m.cpp
8 clang++ $cxxflags -c -fmodule-file=greetings.pcm greetings.cpp
9 clang++ $cxxflags -c greet.cpp
10 # 3. link
11 clang++ $cxxflags -o greet greet.o greetings-m.o greetings.o
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 665
Greet Example: Building With Make
Without Modules
Makefile
1 # This makefile should work with either GCC or Clang.
2 CXXFLAGS = -std=c++20
3 PROGRAMS = greet
4
5 .PHONY: all
6 all: $(PROGRAMS)
7
8 .PHONY: clean
9 clean:
10 rm -f *.o $(PROGRAMS)
11
12 greet: greet.o greetings.o
13 $(CXX) $(CXXFLAGS) -o $@ $^
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 666
Greet Example: Building With Make/GCC
With Modules
Makefile.gcc
1 # This makefile is known to work with a development version of GCC 11.0.0.
2 CXX = g++
3 CXXFLAGS = -std=c++20 -fmodules-ts
4 PROGRAM = greet
5 HEADER_UNITS = iostream string
6
7 .PHONY: all
8 all: $(PROGRAM)
9
10 .PHONY: clean
11 clean:
12 rm -f $(PROGRAM) *.o *.cmi header_units
13 rm -rf gcm.cache
14
15 header_units:
16 for i in $(HEADER_UNITS); do \
17 $(CXX) $(CXXFLAGS) -x c++-system-header -c $$i || exit 1; \
18 done
19 touch header_units
20 greet.cmi: header_units
21 $(CXX) $(CXXFLAGS) -c greetings-m.cpp
22 touch greet.cmi
23 greetings.o: header_units greet.cmi
24 $(CXX) $(CXXFLAGS) -c greetings.cpp
25 greet.o: header_units greet.cmi
26 $(CXX) $(CXXFLAGS) -c greet.cpp
27 greet: greet.o greetings-m.o greetings.o
28 $(CXX) $(CXXFLAGS) -o greet $^
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 667
Greet Example: Building With Make/Clang
With Modules
Makefile.clang
1 # This makefile is known to work with Clang 10.0.1.
2 CXX = clang++
3 CXXFLAGS = \
4 -Wall -std=c++20 -fmodules -stdlib=libc++ -fprebuilt-module-path=.
5 PROGRAM = greet
6
7 .PHONY: all
8 all: $(PROGRAM)
9
10 .PHONY: clean
11 clean:
12 rm -f *.pcm *.o $(PROGRAM)
13
14 greetings.pcm: greetings-m.cpp
15 $(CXX) $(CXXFLAGS) -c --precompile -x c++-module -o $@ $<
16 greetings-m.o: greetings.pcm greetings-m.cpp
17 $(CXX) $(CXXFLAGS) -c greetings-m.cpp
18 greetings.o: greetings.pcm greetings.cpp
19 $(CXX) $(CXXFLAGS) -c -fmodule-file=greetings.pcm greetings.cpp
20 greet.o: greetings.pcm
21 $(CXX) $(CXXFLAGS) -c greet.cpp
22 greet: greet.o greetings-m.o greetings.o
23 $(CXX) $(CXXFLAGS) -o $@ $^
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 668
Benefits of Modules
■ can often reduce compile times (especially in incremental builds) by
avoiding repeated compilation of code in headers
■ greatly reduce risk of many kinds of one-definition rule (ODR) violations
■ provide encapsulation/isolation
2 imports and macros do not leak into or out of module
2 only exported declarations visible to importer of module
■ are import order independent and cannot have cyclic dependencies
■ offer more flexibility in file layout that can be used for code
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 669
Section 2.10.2
Writing Non-Modular Code That Uses Modules
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 670
Import Declarations
■ import directive used to make interface exported by module accessible
■ import declaration can have following forms:
1 import module_name;
2 import header_name;
■ first form imports module named module_name
■ second form imports module generated from importable header
header_name
■ what constitutes importable header to be discussed shortly
■ imports must appear at global scope
■ examples:
import mymodule;
import <iostream>;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 671
Module Import Example
greet.cpp (User of Module)
1 import <iostream>; // import header
2 import greetings; // import module
3
4 int main() {
5 std::cout << greetings::get_greeting() << std::endl;
6 return std::cout ? 0 : 1;
7 }
greetings-m.cpp (Module)
1 export module greetings;
2
3 import <string>; // import header
4
5 export namespace greetings {
6 std::string get_greeting() {return "Hello, World!";}
7 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 672
Importable Headers
■ import of header causes module to be generated that exports all
exportable entities in header
■ referred to as header unit
■ wrappers for C standard library headers not required to be importable
(e.g., cassert, cmath, cstdlib, and cerrno)
■ all other C++ standard library headers are importable
■ whether any other header are importable is implementation defined
■ importable headers intended as transition path from headers to modules
■ importable headers have numerous restrictions:
2 cannot rely on macros or identifiers being predefined
2 cannot rely on macros later being undefined (by importer of header)
2 can only have entity with internal linkage if not used outside header
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 673
Imported Headers As Migration Path From Legacy Code
Legacy Code With Included Headers
hello.cpp Using Included Header
1 #include <iostream>
2
3 int main() {
4 std::cout << "Hello, World!" << std::endl;
5 return !std::cout;
6 }
Code With Imported Headers
hello.cpp Using Imported Header
1 import <iostream>;
2
3 int main() {
4 std::cout << "Hello, World!" << std::endl;
5 return !std::cout;
6 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 674
Section 2.10.3
Writing Modular Code
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 675
Overview of Modules
■ translation unit that is part of module is called module unit
■ every module unit can be classified as exactly one of following:
2 interface unit, which specifies interface provided by module to importers of
module
2 implementation unit, which provides implementation of module’s interface
■ interface (and corresponding implementation) for module can be split
across multiple translation units by using what are called partition units
■ every module unit can also be classified as either partition unit or
non-partition unit
■ module interface unit that is not partition unit called primary module
interface unit
■ only identifiers that module exports are visible to importer of module
■ any code that wants access to exported identifiers of module must first
import that module
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 676
Module Declarations (1)
■ module declaration used to specify that translation unit belongs to module
and to which one
■ module declaration has syntax:
exportopt module name partitionopt attributesopt ;
■ if export specified, declares module interface unit; otherwise, declares
module implementation unit
■ partition consists of colon followed by partition name
■ if partition specified, declares module partition unit
■ some examples of module declarations given below:
Declaration Declared Entity
export module foo; (non-partition) interface unit for module foo
module foo; (non-partition) implementation unit for module foo
export module foo:bar; partition interface unit for partition bar of module
foo
module foo:bar; partition implementation unit for partition bar of
module foo
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 677
Module Declarations (2)
■ module name consists of one or more identifiers separated by dots
■ dots in module names have no special meaning (e.g., no implied
relationship between modules named a, a.b, and a.c)
■ modules names that begin with “std” followed by zero or more digits
reserved for use by standard
■ partition name has same naming restrictions as module name
■ module declaration must be first statement in translation unit, excluding
contents of global module fragment (to be discussed later)
■ cannot have more than one module declaration per translation unit
■ named module must have exactly one primary module interface unit
■ modules and namespaces are independent of one another (i.e., modules
do not impact name lookup except in sense that modules can effect
visibility of identifiers)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 678
Global Module Fragment
■ all declarations made in non-module translation units placed in single
unnamed module known as global module
■ global module is only module that is unnamed
■ module unit can place declarations in global module by specifying global
module fragment
■ global module fragment must come first in translation unit
■ global module fragment started by “module;” and continues until module
declaration
■ can only have preprocessor directives in global module fragment
■ example:
module;
// start of global module fragment
#include <cassert>
#include <cmath>
// end of global module fragment
export module foo; // module declaration
// ...
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 679
General Structure of Module Unit
module; (starts global module fragment)
global module fragment (which can only contain preprocessor directives); for example:
#include <cassert>
module declaration; for example:
export module foo;
import declarations if any; for example:
import <iostream>;
body of module (which starts after first non-import statement and may be empty)
module : private; (starts private module fragment)
private module fragment
■ items shown shaded in gray are optional
■ purview of module unit starts immediately after module declaration and
continues until end of translation unit
■ private module fragment (to be discussed later) only permitted in interface unit
of module comprised of single translation unit
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 680
Additional Remarks
■ purview of module is set of purviews of each of its constituent module
units
■ all module import declarations precede all other declarations in translation
unit
■ in module’s purview, member function defined in body of class not
implicitly inline
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 681
Import Declarations Revisited
■ in module unit, all imports must appear at start of module-unit purview
before any other declarations/statements
■ module implementation unit that is not partition unit implicitly imports its
corresponding module interface unit
■ import that names module partition can only be in same module that to
which module partition belongs
■ module interface unit cannot have interface dependency on itself (i.e.,
cannot directly or indirectly import module that imports module)
■ exported entities in imported header are attached to global module
■ import of header unit behaves as if synthesized module imported
■ macros from header visible in importing module after import directive
■ imported header unit does not depend on ambient preprocessor state at
time of import (i.e., macros cannot leak in)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 682
Example: Greet #1 [With Module Interface Unit Only]
greetings.cpp (Primary Module Interface Unit)
1 export module greetings;
2 import <string>;
3
4 namespace greetings {
5 export std::string get_greeting() {
6 return "Hello, World!";
7 }
8 }
greet.cpp (Non-Module Translation Unit Using Module)
1 import <iostream>;
2 import greetings;
3
4 int main() {
5 std::cout << greetings::get_greeting() << std::endl;
6 return !std::cout;
7 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 683
Example: Greet #2 [With Module Interface and Implementation Units]
greetings-m.cpp (Primary Module Interface Unit)
1 export module greetings;
2 import <string>;
3 namespace greetings {
4 export std::string get_greeting();
5 }
greetings.cpp (Module Implementation Unit)
1 module greetings;
2 import <string>;
3 namespace greetings {
4 std::string get_greeting() {
5 return "Hello, World!";
6 }
7 }
greet.cpp (Normal Translation Unit Using Module)
1 import <iostream>;
2 import greetings;
3 int main() {
4 std::cout << greetings::get_greeting() << std::endl;
5 return !std::cout;
6 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 684
Example: Math #1 [Non-Module Unit; Module Interface Unit]
math-m.cpp (Primary Module Interface Unit for zeus.math Module)
1 module; // start of global module fragment
2 #include <cmath>
3 export module zeus.math; // start of module unit purview
4
5 namespace zeus::math {
6 double answer = 42;
7 export double sinc(double x)
8 {return (x != 0) ? sin(x) / x : 1.0;}
9 export double get_answer() {return answer;}
10 export template <class T> T square(const T& x)
11 {return x * x;}
12 }
app.cpp (Non-Module Unit That Imports zeus.math Module)
1 import <iostream>;
2 import zeus.math;
3
4 int main() {
5 std::cout << zeus::math::square(2.0)
6 << ’ ’ << zeus::math::sinc(0.0)
7 << ’ ’ << zeus::math::get_answer() << ’\n’;
8 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 685
Example: Math #2 [Module Interface and Implementation Units]
math-m.cpp (Primary Module Interface Unit)
1 // no global module fragment
2 // declare module interface unit
3 export module zeus.math; // start of module unit purview
4 namespace zeus::math {
5 export double sinc(double);
6 // note: must export template definition, not just declaration
7 export template <class T> T square(const T& x) {return x * x;}
8 export double get_answer();
9 double answer = 42;
10 }
math.cpp (Module Implem