The document discusses various aspects of template type deduction in C++ including:
1. How the type T and the type of the parameter ParamType are deduced based on whether ParamType is a pointer/reference, universal reference, or neither.
2. How array arguments are handled differently when passed by value versus by reference in templates.
3. How function arguments are treated, with function types decaying to function pointers.
4. The differences between auto type deduction and template type deduction.
5. How decltype can be used to deduce types from expressions while preserving reference-ness.
A hands-on introduction to the ELF Object file formatrety61
In our 6th semester we developed miASMa - a 2 pass Macro Assembler for an x86 machine. miASMa generates Relocatable Object Files that conforming to the ELF Format.
A hands-on introduction to the ELF Object file formatrety61
In our 6th semester we developed miASMa - a 2 pass Macro Assembler for an x86 machine. miASMa generates Relocatable Object Files that conforming to the ELF Format.
We'll discuss our experiences with tooling aimed at finding and fixing performance problems in a production Rust application, as experienced through the eyes of somebody who's more familiar with the Go ecosystem but grew to love Rust. We'll cover CPU and Heap profiling, and also briefly touch causal profiling.
We'll discuss our experiences with tooling aimed at finding and fixing performance problems in a production Rust application, as experienced through the eyes of somebody who's more familiar with the Go ecosystem but grew to love Rust. We'll cover CPU and Heap profiling, and also briefly touch causal profiling.
This presentation considers certain specific features of C++11 and additions to STL library (uniform initialization, new containers and methods, move semantics).
Presentation by Taras Protsiv (Software Engineer, GlobalLogic), Kyiv, delivered at GlobalLogic C++ TechTalk in Lviv, September 18, 2014.
More details -
http://www.globallogic.com.ua/press-releases/lviv-cpp-techtalk-coverage
This PPT discusses the concept of Dynamic Linker as in Linux and its porting to Solaris ARM platform. It starts from the very basics of linking process
[OLD VERSION, SEE DESCRIPTION FOR THE NEWER VERSION LINK] Hot С++: Universal ...Andrey Upadyshev
Newer version: https://www.slideshare.net/oliora/hot-universal-references-and-perfect-forwarding-82155460
Detailed presentation of universal references and perfect forwarding introduced in C++11.
Extended version of "Hot C++11, Part 3 Universal References And Perfect Forwarding".
Hot С++: Universal References And Perfect ForwardingAndrey Upadyshev
Detailed presentation of universal references and perfect forwarding introduced in C++11.
Extended version of "Hot C++11, Part 3 Universal References And Perfect Forwarding".
This is Work-In-Progress. Developing a series of lectures on C++0x. This will augment my presentations on C++ and Design Pattern. First trial run was done at Interra, Noida in 2009
Experience our free, in-depth three-part Tendenci Platform Corporate Membership Management workshop series! In Session 1 on May 14th, 2024, we began with an Introduction and Setup, mastering the configuration of your Corporate Membership Module settings to establish membership types, applications, and more. Then, on May 16th, 2024, in Session 2, we focused on binding individual members to a Corporate Membership and Corporate Reps, teaching you how to add individual members and assign Corporate Representatives to manage dues, renewals, and associated members. Finally, on May 28th, 2024, in Session 3, we covered questions and concerns, addressing any queries or issues you may have.
For more Tendenci AMS events, check out www.tendenci.com/events
How to Position Your Globus Data Portal for Success Ten Good PracticesGlobus
Science gateways allow science and engineering communities to access shared data, software, computing services, and instruments. Science gateways have gained a lot of traction in the last twenty years, as evidenced by projects such as the Science Gateways Community Institute (SGCI) and the Center of Excellence on Science Gateways (SGX3) in the US, The Australian Research Data Commons (ARDC) and its platforms in Australia, and the projects around Virtual Research Environments in Europe. A few mature frameworks have evolved with their different strengths and foci and have been taken up by a larger community such as the Globus Data Portal, Hubzero, Tapis, and Galaxy. However, even when gateways are built on successful frameworks, they continue to face the challenges of ongoing maintenance costs and how to meet the ever-expanding needs of the community they serve with enhanced features. It is not uncommon that gateways with compelling use cases are nonetheless unable to get past the prototype phase and become a full production service, or if they do, they don't survive more than a couple of years. While there is no guaranteed pathway to success, it seems likely that for any gateway there is a need for a strong community and/or solid funding streams to create and sustain its success. With over twenty years of examples to draw from, this presentation goes into detail for ten factors common to successful and enduring gateways that effectively serve as best practices for any new or developing gateway.
Modern design is crucial in today's digital environment, and this is especially true for SharePoint intranets. The design of these digital hubs is critical to user engagement and productivity enhancement. They are the cornerstone of internal collaboration and interaction within enterprises.
Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...informapgpstrackings
Keep tabs on your field staff effortlessly with Informap Technology Centre LLC. Real-time tracking, task assignment, and smart features for efficient management. Request a live demo today!
For more details, visit us : https://informapuae.com/field-staff-tracking/
Enhancing Research Orchestration Capabilities at ORNL.pdfGlobus
Cross-facility research orchestration comes with ever-changing constraints regarding the availability and suitability of various compute and data resources. In short, a flexible data and processing fabric is needed to enable the dynamic redirection of data and compute tasks throughout the lifecycle of an experiment. In this talk, we illustrate how we easily leveraged Globus services to instrument the ACE research testbed at the Oak Ridge Leadership Computing Facility with flexible data and task orchestration capabilities.
Gamify Your Mind; The Secret Sauce to Delivering Success, Continuously Improv...Shahin Sheidaei
Games are powerful teaching tools, fostering hands-on engagement and fun. But they require careful consideration to succeed. Join me to explore factors in running and selecting games, ensuring they serve as effective teaching tools. Learn to maintain focus on learning objectives while playing, and how to measure the ROI of gaming in education. Discover strategies for pitching gaming to leadership. This session offers insights, tips, and examples for coaches, team leads, and enterprise leaders seeking to teach from simple to complex concepts.
Developing Distributed High-performance Computing Capabilities of an Open Sci...Globus
COVID-19 had an unprecedented impact on scientific collaboration. The pandemic and its broad response from the scientific community has forged new relationships among public health practitioners, mathematical modelers, and scientific computing specialists, while revealing critical gaps in exploiting advanced computing systems to support urgent decision making. Informed by our team’s work in applying high-performance computing in support of public health decision makers during the COVID-19 pandemic, we present how Globus technologies are enabling the development of an open science platform for robust epidemic analysis, with the goal of collaborative, secure, distributed, on-demand, and fast time-to-solution analyses to support public health.
Advanced Flow Concepts Every Developer Should KnowPeter Caitens
Tim Combridge from Sensible Giraffe and Salesforce Ben presents some important tips that all developers should know when dealing with Flows in Salesforce.
First Steps with Globus Compute Multi-User EndpointsGlobus
In this presentation we will share our experiences around getting started with the Globus Compute multi-user endpoint. Working with the Pharmacology group at the University of Auckland, we have previously written an application using Globus Compute that can offload computationally expensive steps in the researcher's workflows, which they wish to manage from their familiar Windows environments, onto the NeSI (New Zealand eScience Infrastructure) cluster. Some of the challenges we have encountered were that each researcher had to set up and manage their own single-user globus compute endpoint and that the workloads had varying resource requirements (CPUs, memory and wall time) between different runs. We hope that the multi-user endpoint will help to address these challenges and share an update on our progress here.
Providing Globus Services to Users of JASMIN for Environmental Data AnalysisGlobus
JASMIN is the UK’s high-performance data analysis platform for environmental science, operated by STFC on behalf of the UK Natural Environment Research Council (NERC). In addition to its role in hosting the CEDA Archive (NERC’s long-term repository for climate, atmospheric science & Earth observation data in the UK), JASMIN provides a collaborative platform to a community of around 2,000 scientists in the UK and beyond, providing nearly 400 environmental science projects with working space, compute resources and tools to facilitate their work. High-performance data transfer into and out of JASMIN has always been a key feature, with many scientists bringing model outputs from supercomputers elsewhere in the UK, to analyse against observational or other model data in the CEDA Archive. A growing number of JASMIN users are now realising the benefits of using the Globus service to provide reliable and efficient data movement and other tasks in this and other contexts. Further use cases involve long-distance (intercontinental) transfers to and from JASMIN, and collecting results from a mobile atmospheric radar system, pushing data to JASMIN via a lightweight Globus deployment. We provide details of how Globus fits into our current infrastructure, our experience of the recent migration to GCSv5.4, and of our interest in developing use of the wider ecosystem of Globus services for the benefit of our user community.
Code reviews are vital for ensuring good code quality. They serve as one of our last lines of defense against bugs and subpar code reaching production.
Yet, they often turn into annoying tasks riddled with frustration, hostility, unclear feedback and lack of standards. How can we improve this crucial process?
In this session we will cover:
- The Art of Effective Code Reviews
- Streamlining the Review Process
- Elevating Reviews with Automated Tools
By the end of this presentation, you'll have the knowledge on how to organize and improve your code review proces
Multiple Your Crypto Portfolio with the Innovative Features of Advanced Crypt...Hivelance Technology
Cryptocurrency trading bots are computer programs designed to automate buying, selling, and managing cryptocurrency transactions. These bots utilize advanced algorithms and machine learning techniques to analyze market data, identify trading opportunities, and execute trades on behalf of their users. By automating the decision-making process, crypto trading bots can react to market changes faster than human traders
Hivelance, a leading provider of cryptocurrency trading bot development services, stands out as the premier choice for crypto traders and developers. Hivelance boasts a team of seasoned cryptocurrency experts and software engineers who deeply understand the crypto market and the latest trends in automated trading, Hivelance leverages the latest technologies and tools in the industry, including advanced AI and machine learning algorithms, to create highly efficient and adaptable crypto trading bots
A Comprehensive Look at Generative AI in Retail App Testing.pdfkalichargn70th171
Traditional software testing methods are being challenged in retail, where customer expectations and technological advancements continually shape the landscape. Enter generative AI—a transformative subset of artificial intelligence technologies poised to revolutionize software testing.
Check out the webinar slides to learn more about how XfilesPro transforms Salesforce document management by leveraging its world-class applications. For more details, please connect with sales@xfilespro.com
If you want to watch the on-demand webinar, please click here: https://www.xfilespro.com/webinars/salesforce-document-management-2-0-smarter-faster-better/
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...Globus
Large Language Models (LLMs) are currently the center of attention in the tech world, particularly for their potential to advance research. In this presentation, we'll explore a straightforward and effective method for quickly initiating inference runs on supercomputers using the vLLM tool with Globus Compute, specifically on the Polaris system at ALCF. We'll begin by briefly discussing the popularity and applications of LLMs in various fields. Following this, we will introduce the vLLM tool, and explain how it integrates with Globus Compute to efficiently manage LLM operations on Polaris. Attendees will learn the practical aspects of setting up and remotely triggering LLMs from local machines, focusing on ease of use and efficiency. This talk is ideal for researchers and practitioners looking to leverage the power of LLMs in their work, offering a clear guide to harnessing supercomputing resources for quick and effective LLM inference.
2. Template Type Deduction
• The Type deduction for T is dependent not just on
the type of expr, but also on the form of
ParamType.
• ParamType is a pointer or reference type.
• ParamType is a universal reference.
• ParamType is neither a pointer nor a reference.
template<typename T>
void f(ParamType param);
f(expr)
3. ParamType is a pointer or reference type
1. If expr’s type is a reference, ignore the reference part.
2. Then pattern-match expr’s type against ParamType to
determine T.
template<typename T>
void f(T& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // T is int, param’s type is int&
f(cx); // T is const int, param’s type is const int&
f(rx); // T is const int, param’s type is const int&
4. ParamType is a universal reference
• If expr is an lvalue, both T and ParamType are deduced to be lvalue references.
• It’s the only situation in template type deduction where T is deduced to be a
reference.
• ParamType is declared using the syntax for an rvalue reference, its deduced
type is an lvalue reference.
• If expr is an rvalue, the Case 1 rules apply.
template<typename T>
void f(T&& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // T is int&, param’s type is int&
f(cx); // T is const int&, param’s type is const int&
f(rx); // T is const int&, param’s type is const int&
f(27); // T is int, param’s type is int&&
5. ParamType is neither a pointer nor a reference
template<typename T>
void f(T param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // T is int, param’s type is int
f(cx); // T is int, param’s type is int
f(rx); // T is int, param’s type is int
• Param will be a copy of whatever is passed in.
1. If expr’s type is a reference, ignore the reference part.
2. If expr is const, ignore that. If it’s volatile, also ignore that.
6. Array Arguments
template<typename T>
void f(T param);
// The type of an array passed to a template function by value is deduced
// to be a pointer type.
const char name[] = “J. P. Briggs”;
f(name); // T deduced as const char *
// Arrays decay into pointer.
template<typename T>
void f(T& param); // passed to a template function by reference
f(name); // T deduced to be const char [13]
// Param’s type is const char (&)[13]
7. Array Arguments
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
return N;
}
int keyVals[] = {1, 3, 7, 9, 11, 22, 35};
int mappedVals[arraySize(keyVals)];
std::array<int, arraySize(keyVals)> mappedVals;
• The ability to declare references to array enables creation of a
template that deduces the number of elements that an array
contains.
8. Function Arguments
void someFunc(int, double); // type is void (int, double)
template<typename T>
void f1(T param); // pass by value
template<typename T>
void f2(T& param); // pass by reference
f1(someFunc); // param’s type is void (*)(int, double)
// Function types decay into function pointers.
f2(someFunc); // param’s type is void (&)(int, double)
9. auto type deduction
auto x = 27;
const auto cx = x;
const auto& rx = x;
template<typename T>
void x(T param);
x(27)
template<typename T>
void cx(const T param);
cx(x);
template<typename T>
void rx(const T& param);
rx(x);
With only one exception, auto type deduction is template type deduction.
1. The type specifier is a pointer or reference, but not a universal reference.
2. The type specifier is a universal reference.
3. The type specifier is neither a pointer nor a reference.
10. auto type deduction
auto x1 = 27; // int
auto x2(27); // int
auto x3 = {27}; // std::initializer_list<int>
auto x4{27}; // std::initializer_list<int>
When the initialiser for an auto-declared variable is enclosed in braces,
the deduced type is a std::initializer_lists.
auto x = {11, 23, 9};
template<typename T>
void x(T param);
x({11, 23, 9}); // error! can’t deduce type
auto assumes that a braced initialiser represents a std::initializer_list,
but template type deduction doesn’t.
template<typename T>
void x(std::initializer_list<T> param);
x({11, 23, 9}); // T deduced as int, param’s type is std::initializer_list<int>
11. auto type deduction
C++14:
1. permit auto to indicate that a function’s return type should be deduced.
2. lambdas may use auto in parameter declarations.
these use of auto employ template type deduction, not auto type deduction.
auto createInitList()
{
return {1, 2, 3}; // error: can’t deduce type for {1, 2, 3}
}
12. • Given a name or an expression, decltype tells you the name’s or the
expression’s type.
• The primary use for decltype is declaring function templates where the
function’s return type depends on its parameter types.
decltype
// C++11
template<typename Container, typename Index>
auto autoAndAccess(Container& c, Index i)
-> decltype(c[i])
{
authenticateUser();
return c[i];
}
// C++14
template<typename Container, typename Index>
auto autoAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i]; // return type deduced from c[i]
}
13. • To prevent to remove reference-ness of an initialising expression.
(Rule 3 in template type deduction.)
decltype
// C++14
template<typename Container, typename Index>
decltype(auto) autoAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i]; // return type deduced from c[i]
}
std::deque<int> d;
authAndAccess(d, 5); // d[5] returns an int&
14. decltype
// C++14
Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // myWidget1’s type is Widget
// const-ness and reference-ness will be removed
decltype(auto) myWidget2 = cw; // myWidget2’s type is const Widget&
16. decltype
• Applying decltype to a name yields the declared type for that name.
• For lvalue expressions more complicated than names, decltype
ensures that the type reported is always an lvalue reference.
// C++14
decltype(auto) f1()
{
int x = 0;
…
return x; // decltype(x) is int, so f1 returns int
}
decltype(auto) f2()
{
int x = 0;
…
return (x); // decltype((x)) is int&, so f2 returns int&
// return a reference to a local variable! (undefined behavior)
}
17. auto - must be initialised
• auto variables have their type deduced from their initialiser, so they
must be initialised.
int x1; // potentially uninitialised
auto x2; // error! initialiser required
auto x3 = 0; // fine, x’s value is well-defined
18. auto - avoid verbose
template<typename It>
void dwim(It b, It e)
{
while (b != e) {
typename std::iterator_traits<It>::value_type
currValue = *b;
…
}
}
template<typename It>
void dwim(It b, It e)
{
while (b != e) {
auto currValue = *b;
…
}
}
• avoid verbose variable declarations
19. auto - hold a closure
• std::function is a template in the C++11 Standard Library that
generalised the idea of a function pointer.
• Using std::function is not the same as using auto.
• An auto-declared variable holding a closure has the same type as
the closure, it uses only as much memory as the closure requires.
std::function<bool(const std::unique_ptr<Widget>&,
const std::unique_ptr<Widget>&)>
derefUPLess = [](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{ return *p1 < *p2; };
// C++11
auto derefUPLess = [](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{ return *p1 < *p2; };
// C++14
auto derefUPLess = [](const auto& p1,
const auto& p2)
{ return *p1 < *p2; };
20. auto
• The type of a std::function-declared variable holding a closure is an
instantiation of the std::function template, and that has a fixed size
for any given signature.
• The std::function approach is generally bigger and slower than the
auto approach, and it may yield out-of-memory exceptions.
std::function<bool(const std::unique_ptr<Widget>&,
const std::unique_ptr<Widget>&)>
derefUPLess = [](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{ return *p1 < *p2; };
// C++11
auto derefUPLess = [](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{ return *p1 < *p2; };
// C++14
auto derefUPLess = [](const auto& p1,
const auto& p2)
{ return *p1 < *p2; };
21. auto
• Use auto to avoid potential bugs and performance issues.
std::vector<int> v;
unsigned sz = v.size(); // potential bugs. unsigned is 32-bits on 64-bits machine
// v.size()’s type is std::vector<int>::size_type
auto sz = v.size(); // sz’s type is std::vector<int>::size_type
std::unordered_map<std::string, int> m;
// Key value is const, so actual type is std::pair<const std::string, int>
// There will be a temporary variable of std::pair<std::string, int>
// Every loop has an extra constructor and destructor
for (const std::pair<std::string, int>& p : m) {
…
}
// avoid temporary variable
// simply bind the reference p to each element in m
for (const auto& p : m) {
…
}
22. auto
• operator[] for std::vector<bool> doesn’t return a reference to
an element of the container. C++ forbids references to bits.
• It returns an object of type std::vector<bool>::reference
std::vector<bool> features(const Widget& w);
Widget w;
auto highPriority = features(w)[5]; // deduced type is std::vector<bool>::reference
// highPriority is a reference to a temporary
// variable. It is a dangling pointer.
processWidget(w, highPriority); // undefined behaviour!
std::vector<bool> features(const Widget& w);
Widget w;
bool highPriority = features(w)[5]; // std::vector<bool>::reference implicit
// convert to bool
processWidget(w, highPriority);
23. auto
• std::vector<bool>::reference is a proxy class that exists for the
purpose of emulating and augmenting the behaviour of some other type.
• As a general rule, proxy classes don’t play well with auto.
• Objects of such classes are often not designed to live longer than a
single statement.
// solution
std::vector<bool> features(const Widget& w);
Widget w;
auto highPriority = static_cast<bool>(features(w)[5]); // explicitly typed
processWidget(w, highPriority);
// avoid this
auto someVar = expression of proxy class type
24. Brace Initialiser
• There are three methods to initialise variables.
int x(0); // initialiser is in parentheses
int y = 0; // initialiser follows “=“
int z{ 0 }; // initialiser is in braces
• There were some situations where c++98 had no way to express a
desired initialisation.
std::vector<int> v{1, 3, 5}; // (C++11) v’s initial content is 1, 3, 5
25. Brace Initialiser
• Brace initialiser is universal initialiser.
// C++11. Braces can be used to initialise non-static data members.
class Widget {
…
private:
int x{0}; // fine, x’s default value is 0
int y = 0; // also fine
int z(0); // error!
};
// uncopyable objects may be initialised using braces or parentheses.
std::atomic<int> ai1{0}; // fine
std::atomic<int> ai2(0); // fine
std::atomic<int> ai3 = 0; // error!
26. Brace Initialiser
• Brace initialiser prohibits implicit narrowing conversions.
double x, y, z;
int sum1{x + y + z}; // error! sum of doubles may not be expressible as int
int sum2(x + y + z); // okay
int sum3 = x + y + z; // okay
27. Brace Initialiser
• std::initializer_list overshadows other constructors to the point
where the other overloads may hardly be considered.
class Widget {
public:
Widget(int i, bool b);
Widget(int i, double d);
Widget(std::initializer_list<long double> il);
…
};
Widget w1(10, true); // call first ctor
Widget w2{10, true}; // call std::initializer_list ctor
Widget w3(10, 5.0); // call second ctor
Widget w4{10, 5.0}; // call std::initializer_list ctor
28. Brace Initialiser
• Most developers end up choosing one kind of delimiter as a default, using
the other only when they have to.
29. nullptr
• 0 and NULL are integral type, not pointer type.
• nullptr’s type is std::nullptr_t.
The type implicitly converts to all raw pointer types.
void f(int);
void f(bool);
void f(void*);
f(0); // call f(int), not f(void*)
f(NULL); // might not compile, but typically calls f(int)
30. nullptr
• improve code clarity
auto result = findRecord();
if (result == 0) { // the return value may be int or pointer
…
}
auto result = findRecord();
if (result == nullptr) { // the return value must be pointer, it is more clear.
…
}
31. nullptr
• template type deduction
template<typename FuncType,
typename MuxType,
typename PtrType>
auto lockAndCall(FuncType func, MuxType mutex, PtrType ptr) -> decltype(func(ptr))
{
using MuxGuard = std::lock_guard<MuxType>;
MuxGuard g(mutex); // lock mutex
return func(ptr); // call function
} // unlock mutex
int f1(std::shared_ptr<Widget> spw); // call these only when
double f2(std::unique_ptr<Widget> upw); // the appropriate
bool f3(Widget* pw); // mutex is locked
std::mutex f1m, f2m, f3m;
auto result1 = lockAndCall(f1, f1m, 0); // error! ptr’s type is deduced to int
auto result2 = lockAndCall(f2, f2m, NULL); // error!
auto result3 = lockAndCall(f3, f3m, nullptr); // fine. ptr’s type is std::nullptr_t
// implicitly convert to Widget*
32. Alias Declarations
// use typedef
typedef
std::unique_ptr<std::unordered_map<std::string, std::string>>
UPtrMapSS;
typedef void (*FP)(int, const std::string&);
// use alias declaration
using UPtrMapSS =
std::unique_ptr<std::unordered_map<std::string, std::string>>
using FP = void (*)(int, const std::string&);
33. Alias Declarations
// alias declaration
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;
MyAllocList<Widget> lw;
// typedef
template<typename T>
struct MyAllocList {
typedef std::list<T, MyAlloc<T>> type;
};
MyAllocList<Widget>::type lw;
• Alias declarations may be templatized, while typedefs cannot.
34. Alias Declarations
template<typename T>
struct MyAllocList {
typedef std::list<T, MyAlloc<T>> type;
};
template<typename T>
class Widget {
private:
typename MyAllocList<T>::type list; // MyAllocList<T> may be a specialization.
// “type” may be a data member of it
// The names of dependent types must
// be preceded by typename
…
};
• typedef inside a template class
35. Scoped enum
enum Color { black, white, red }; // unscoped enum
auto white = false; // error! white already declared in this scope
enum class Color { black, white, red }; // scoped enum. avoid name pollution
auto white = false; // fine.
• The name of unscoped enum belong to the scope containing the enum.
36. Scoped enum
enum class Color { black, white, red }; // scoped enum
std::vector<std::size_t>
primeFactors(std::size_t x);
Color c = Color::white; // fine.
if (c < 14.5) { // error! can’t convert to double
auto factors = primeFactors(c); // error! can’t convert to std::size_t
…
}
• Scoped enum is strongly typed
37. Scoped enum
enum class Color; // fine.
enum Color; // error!
• Scoped enum could be forward-declared
• If unscoped enum wants to be forward-declared, it needs to specify
underlying type.
enum Color: std::uint8_t; // forward-declaration for unscoped enum
38. deleted function
// C++98
template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
public:
…
private;
basic_ios(const basic_ios& ); // not defined (link-time error)
basic_ios& operator=(const basic_ios& ); // not defined
}
• prevent calling some particular functions
// C++11
template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
public: // C++ checks accessibility before deleted status.
// Let compiler show more clear messages.
…
basic_ios(const basic_ios& ) = delete; // if used, it’s a compile-time
basic_ios& operator=(const basic_ios& ) = delete; // error.
…
}
39. deleted function
template<typename T>
void processPointer(T* ptr);
template<>
void processPointer<void>(void*) = delete;
template<>
void processPointer<char>(char*) = delete;
• prevent use of template instantiations that should be disabled
// template member function
class Widget {
public:
template<typename T>
void processPointer(T* ptr) { … }
};
template<>
void Widget::processPointer<void>(void*) = delete;
40. override
class Base {
public:
virtual void mf1() const;
virtual void mf2(int x);
virtual void mf3() &;
};
class Derived : public Base {
public:
virtual void mf1() const override;
virtual void mf2(int x) override;
virtual void mf3() & override;
};
• make explicit that a derived class function is supposed to override
a base class version
41. Prefer const_iterators to iterators
std::vector<int> values
auto it = std::find(values.cbegin(), values.cend(), 1983);
values.insert(it, 1998);
• The standard practice of using const whenever possible dictates that you
should use const_iterators any time you need an iterator.
• There is no portable conversion from a const_iterator to an iterator,
not even with a static_cast.
42. constexpr objects
int sz;
constexpr auto arraySize1 = sz; // error! sz’s value not known at compilation
std::array<int, sz> data1; // error!
constexpr auto arraySize2 = 10;
std::array<int, arraySize2> data2; // fine. arraySize2 is constexpr
int sz2;
const auto arraySize = sz2; // fine. arraySize is const copy of sz2
std::array<int, arraySize> data; // error! arraySize’s value not known at compilation
• constexpr indicates a value that’s not only constant, it’s known
during compilation.
• constexpr objects are, in fact, const, and they do have values that are
known at compile time.
43. constexpr functions
constexpr
int pow(int base, int exp) noexcept
{
…
}
constexpr auto numConds = 5;
std::array<int, pow(3, numConds)> results; // call pow function at compile-time
auto base = readFromDB(“base”);
auto exp = readFromDB(“exponent”);
auto baseToExp = pow(base, exp); // call pow function at runtime
• constexpr functions produce compile-time constants when they are called
with compile-time constants. If they’re called with values not known until
runtime, they produce runtime values.
44. constexpr
// C++11
constexpr int pow(int base, int exp) noexcept
{
return (exp == 0 ? 1 : base * pow(base, exp - 1));
}
// C++14
constexpr int pow(int base, int exp) noexcept
{
auto result = 1;
for (int i = 0; i < exp; i++)
result *= base;
return result;
}
• In C++11, constexpr functions may contain no more than a single
executable statement: a return.
45. constexpr
class Point {
public:
constexpr Point(double xVal = 0, double yVal = 0) noexcept
: x(xVal), y(yVal)
{}
constexpr double xValue() const noexcept { return x; }
constexpr double yValue() const noexcept { return y; }
void setX(double newX) noexcept { x = newX; } // setX could be constexpr in C++14
void setY(double newY) noexcept { y = newY; }
private:
double x, y;
};
constexpr Point p1(9.4, 27.7); // “runs” constexpr actor during compilation
constexpr Point p2(28.8, 5.3);
constexpr Point midpoint(const Point& p1, const Point& p2) noexcept
{
return { (p1.xValue() + p2.xValue()) / 2, (p1.yValue() + p2.yValue()) / 2};
}
constexpr auto mid = midpoint(p1, p2);
• constructors and other member functions may be constexpr
46. make const member functions thread safe
• roots doesn’t change Polynomial object on which it operates, but as
part of its caching activity, it may need to modify rootVals and
rootsAreValid.
• roots is declared const, but it’s not thread safe.
class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const
{
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
};
47. make const member functions thread safe
• std::mutex is a move-only type. A side effect of adding m to Polynomial
is that Polynomial loses the ability to be copied.
class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const
{
std::lock_guard<std::mutex> g(m);
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
mutable std::mutex m;
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
};
48. make const member functions thread safe
• std::atomic variables are often less expensive than mutex.
• For a single variable or memory location requiring synchronization, use
of a std::atomic is adequate.
class Point {
public:
double distanceFromOrigin() const noexcept
{
++callCount;
return std::sqrt((x * x) + (y * y));
};
private:
mutable std::atomic<unsigned> callCount{ 0 };
double x, y;
};
49. special member function generation
• the default constructor
• the destructor
• the copy constructor
• the copy assignment operator
• the move constructor (C++11)
• the move assignment operator (C++11)
class Widget {
public:
Widget(Widget&& rhs); // move constructor
widget& operator=(Widget&& rhs); // move assignment operator
};
50. special member function generation
• the move constructor (C++11)
• the move assignment operator (C++11)
• perform “memberwise moves” on the non-static data members
• “memberwise moves” are more like member wise move requests.
• memberwise move consists of move operations on data members
and base classes that support move operations, but a copy operation
for those that don’t.
• the two move operations are not independent.
• declaring a move constructor prevents a move assignment operator
from being generated.
• declaring a move assignment operator prevents compilers from
generating a move constructor.
• move operations won’t be generated for any class that explicitly declares
a copy operation.
• C++11 does not generate move operations for a class with a user-declared
destructor.
• The Rule of Three: if you declare any of a copy constructor, copy
assignment operator, or destructor, you should declare all three.
51. special member function generation
• So move operations are generated for classes only if these three things are
true:
• No copy operations are declared in the class.
• No move operations are declared in the class.
• No destructor is declared in the class.
52. std::unique_ptr
• std::unique_ptr embodies exclusive ownership semantic.
• Copying a std::unique_ptr isn’t allowed.
• By default, that destruction would take place via delete, but, during
construction, std::unique_ptr objects can be configured to use custom
deleters.
auto delInvmt = [](Investment* pInvestment) // accept a raw pointer as argument
{
makeLogEntry(pInvestment);
delete pInvestment;
};
template<typename… Ts>
std::unique_ptr<Investment, decltype(delInvmt)> // specified in unique_ptr
makeInvestment(Ts&&… params)
{
std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt);
if ( /* a Stock object should be created */ )
{
pInv.reset(new Stock(std::forward<Ts>(params)…);
}
return pInv;
}
53. std::unique_ptr
• Deleters that are function pointers generally cause the size of a
std::unique_ptr to grow from one word to two.
• For deleters that are function objects, the change in size depends on
how much state is stored in the function object.
• Stateless function objects (lambda expressions) incur no size penalty.
54. std::unique_ptr
• std::unique_ptr comes in two forms, one for individual objects )
std::unique_ptr<T>) and one for arrays (std::unique_ptr<T[]>).
• One of its most attractive features is that it easily and efficiently converts to
a std::shared_ptr.
• std::shared_ptr can’t work with arrays.
55. std::shared_ptr
• When the last std::shared_ptr pointing to an object stops pointing there,
that std::shared_ptr destroys the object it points to.
• A std::shared_ptr can tell whether it’s the last one pointing to a resource
by consulting the resource’s reference count.
• std::shared_ptr are twice the size of a raw pointer.
• Memory for the reference count must be dynamically allocated.
• Increments and decrements of the reference count must be atomic.
56. std::shared_ptr Custom Deleters
• For std::unique_ptr, the type of the deleter is part of the type of the smart
pointer. For std::shared_ptr, it’s not.
• Specifying a custom deleter doesn’t change the size of a std::shared_ptr
object.
auto loggingDel = [](Widget* pw)
{
makeLogEntry(pw);
delete pw;
};
std::unique_ptr<Widget, decltype(loggingDel)> upw(new Widget, loggingDel);
std::shared_ptr<Widget> spw(new Widget, loggingDel);
pointer to T
pointer to Contrl Block
std::shared_ptr<T>
T Object
Reference Count
Weak Count
Custom Deleter
57. std::shared_ptr Control Block
• An object’s control block is set up by the function creating the first
std::shared_ptr to the object.
• std::make_shared always creates a control block.
• A control block is created when a std::shared_ptr is constructed from
a unique-ownership pointer.
• When a std::shared_ptr constructor is called with a raw pointer, it
creates a control block.
auto pw = new Widget; // The creation of the raw pointer is bad.
std::shared_ptr<Widget> spw1(pw, loggingDel); // create control block
std::shared_ptr<Widget> spw2(pw, loggingDel); // create 2nd control block
// It will cause undefined behavior.
std::shared_ptr<Widget> spw1(new Widget, loggingDel); // direct use of new
std::shared_ptr<Widget> spw2(spw1); // spw2 uses same control block as spw1
59. std::shared_ptr
std::vector<std::shared_ptr<Widget>> processedWidgets;
// The Curiously Recurring Template Pattern (CRTP)
class Widget : public std::enable_shared_from_this<Widget> {
public:
// To prevent clients from calling member functions that invoke shared_from_this
// before a std::shared_ptr points to the object.
template<typename… Ts>
static std::shared_ptr<Widget> create(Ts&&… params); // factory method
…
void process();
…
private:
… // private ctors
};
void Widget::process()
{
processWidgets.emplace_back(shared_from_this()); // There must be an existing
// std::shared_ptr that points
// to the current object.
}
60. std::weak_ptr
• A pointer like std::shared_ptr that doesn’t affect an object’s
reference count.
• std::weak_ptr isn’t a standalone smart pointer. It’s an augmentation of
std::shared_ptr.
auto spw = std::make_shared<Widget>();
std::weak_ptr<Widget> wpw(spw); // wpw points to same Widget as spw.
spw = nullptr; // wpw now dangles.
if (wpw.expired()) … // if wpw doesn’t point to an object…
// “check then create” should be atomic.
// creating a shared_ptr from the std::weak_ptr
// 1. use weak_ptr::lock
std::shared_ptr<Widget> spw1 = wpw.lock(); // if wpw’s expired, spw1 is null
auto spw2 = wpw.lock(); // same as above, but uses auto
// 2. use constructor
std::shared_ptr<Widget> spw3(wpw); // if wpw’s expired, throw std::bad_weak_ptr
61. std::weak_ptr
• Caching
• Observer design pattern
• keep a list of observers
• Backward pointer
// caching example
std::unique_ptr<const Widget> loadWidget(WidgetID id); // costly version
std::shared_ptr<const Widget> fastLoadWidget(WidgetID id) // caching version
{
static std::unordered_map<WidgetID, std::weak_ptr<const Widget>> cache;
auto objPtr = cache[id].lock();
if (!objPtr) {
objPtr = loadWidget(id); // load the object
cache[id] = objPtr; // cache it
}
return objPtr;
}
A B C
std::shared_ptr std::shared_ptr
std::weak_ptr
62. std::make_unique && std::make_shared
• std::make_shared is part of C++11.
• std::make_unique is part of C++14.
• This form of the function doesn’t support arrays or custom deleters.
• std::allocate_shared acts like std::make_shared, except
its first argument is an allocator object to be used for the dynamic
memory allocation.
// make_unique implementation for C++11
template<typename T, typename… Ts>
std::unique_ptr<T> make_unique(Ts&&… params)
{
return std::unique_ptr<T>(new T(std::forward<Ts>(params)…));
}
auto upw1(std::make_unique<Widget>()); // with make function
std::unique_ptr<Widget> upw2(new Widget); // without make function
auto spw1(std::make_shared<Widget>()); // with make function
std::shared_ptr<Widget> spw2(new Widget); // without make function
63. std::make_unique && std::make_shared
int computePriority();
// Before calling processWidget,
// * The expression “new Widget” must be evaluated
// * The constructor for the std::shared_ptr<Widget>
// * computePriority must run
processWidget(std::shared_ptr<Widget>(new Widget), computePriority());
// If compiler emits code as follow
// 1. Perform “new Widget”
// 2. Execute computePriority
// 3. Run std::shared_ptr constructor
// If (2) produces an exception, the dynamically allocated memory will be leaked.
// std::make_shared has no such problems.
processWidget(std::make_shared<Widget>(), computePriority());
64. std::make_unique && std::make_shared
// Two allocations.
// 1. allocate memory for Widget
// 2. allocate memory for control block of std::shared_ptr
std::shared_ptr<Widget> spw(new Widget);
// One allocation suffices.
// Caveat: Widget memory will not free until no one uses control block.
auto spw = std::make_shared<Widget>();
65. std::make_unique && std::make_shared
• None of the make functions permit the specification of custom deleters.
• Within the make functions, the perfect forwarding code uses parentheses,
not braces.
• Using make functions to create objects of types with class-specific versions
of operator new and operator delete is typically a poor idea.
66. std::make_unique && std::make_shared
• Exception safe code without make functions.
void processWidget(std::shared_ptr<Widget> spw, int priority);
void cusDel(Widget *ptr); // custom deleter
// Put allocation of the Widget and the construction of the std::shared_ptr
// into one statement.
std::shared_ptr<Widget> spw(new Widget, cusDel); // exception safe
processWidget(std::move(spw), computePriority()); // use move() to turn lvalue
// to rvalue. It is more
// efficient.
// Because processWidget’s first parameter is passed by value, construction
// from an rvalue entails only a move, while construction from an lvalue requires
// a copy.
67. Pimpl Idiom
// Original class
class Widget {
public:
Widget();
…
private:
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
// Pimpl Idiom
class Widget {
public:
Widget();
~Widget(); // door is needed
private:
struct Impl; // declare implementation struct
Impl *pImpl; // and pointer to it
};
69. Pimpl Idiom (std::unique_ptr)
class Widget { // in header “widget.h”
public:
Widget();
~Widget(); // declaration only
Widget(Widget&& rhs); // declaration only
Widget& operator=(Widget&& rhs);
Widget(const Widget& rhs); // declaration only
Widget& operator=(const Widget& rhs);
private:
struct Impl; // incomplete type declaration
std::unique_ptr<Impl> pImpl;
};
70. Pimpl Idiom (std::unique_ptr)
#include “widget.h” // in impl. file “widget.cpp”
#include “gadget.h”
#include <string>
#include <vector>
struct Widget::Impl {
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget()
: pImpl(std::make_unique<Impl>()) {}
// dtor use static_assert to ensure that the
// raw pointer is complete type
Widget::~Widget() = default; // destructor needs to see complete type
// generate code to destroy pImpl when exception arises
// (need complete type)
Widget::Widget(Widget&& rhs) = default;
// need to destroy the object pointed to by pImpl
// before reassigning it. (need complete type)
Widget& Widget::operator=(Widget&& rhs) = default;
Widget::Widget(const Widget& rhs) // deep copy
: pImpl(std::make_unique<Impl>(*rhs.pImpl)) {}
Widget& Widget::operator=(const Widget& rhs)
{
*pImpl = *rhs.pImpl;
return *this;
}
71. std::move
// C++11
template<typename T>
typename remove_reference<T>::type&& // ensuring that && is applied to a type that isn’t a reference.
move(T&& param)
{
using ReturnType =
typename remove_reference<T>::type&&;
return static_cast<ReturnType>(param);
}
// C++14
template<typename T>
decltype(auto) move(T&& param)
{
using ReturnType = remove_reference_t<T>&&;
return static_cast<ReturnType>(param);
}
• std::move does nothing but cast its argument to an rvalue.
72. std::move
class Annotation {
public:
explicit Annotation(const std::string text)
: value(std::move(text)) // move will convert to const std::string&&
{ … } // It will call copy ctor, not move ctor.
…
private:
std::string value;
};
class string {
public:
…
string(const string& rhs); // copy ctor
string(string&& rhs); // move ctor
};
• Don’t declare objects const if you want to be able to move from them.
• std::move doesn’t guarantee that the object it’s casting will be eligible
to be moved.
73. std::forward
void process(const Widget& lvalArg);
void process(Widget&& rvalArg);
template<typename T>
void logAndProcess(T&& param) // universal reference
{
auto now = std::chrono::system_clock::now();
makeLogEntry(“Calling ‘process’”, now);
process(std::forward<T>(param)); // T will convey rvalue or lvalue information.
}
Widget w;
logAndProcess(w); // call with lvalue
logAndProcess(std::move(w)); // call with rvalue
• std::forward casts to an rvalue only if its argument was initialised with
an rvalue.
74. rvalue reference and universal reference
void f(Widget&& param); // rvalue reference
Widget&& var1 = Widget(); // rvalue reference
auto&& var2 = var1; // universal reference (auto declaration)
template<typename T>
void f(std::vector<T>&& param); // rvalue reference
template<typename T>
void f(T&& param); // universal reference (function template parameter)
template<typename T>
void f(const T&& param); // rvalue reference
• Universal references arise in two context.
• function template parameters (It must be precisely “T&&”.)
• auto declarations
• What these contexts have in common is the presence of type deduction.
75. rvalue reference and universal reference
template<class T, class Allocator = allocator<T>>
class vector {
public:
void push_back(T&& x); // rvalue reference
template <class… Args>
void emplace_back(Args&&… args); // universal reference
…
};
auto timeFuncInvocation =
[](auto&& func, auto&&… params) // universal reference
{
// start timer;
std::forward<decltype(func)>(func)(
std::forward<decltype(params)>(params)…);
// stop timer and record elapsed time;
};
76. Use std::forward on universal reference
// Example 1
class Widget {
public:
template<typename T>
void setName(T&& newName) // universal reference
{ name = std::move(newName); } // bad idea to use move() on universal reference
…
private:
std::string name;
};
std::string getWidgetName();
Widget w;
auto n = getWidgetName();
w.setName(n); // move n into w
// n’s value now unknown (potential error!)
// Example 2
template<typename T>
Fraction
reduceAndCopy(T&& frac)
{
frac.reduce();
return std::forward<T>(frac); // move rvalue into return value, copy lvalue
}
77. Use std::move on rvalue reference
Matrix
operator+(Matrix&& lhs, const Matrix& rhs)
{
lhs += rhs;
return std::move(lhs); // move lhs into return value (more efficient)
}
Matrix
operator+(Matrix&& lhs, const Matrix& rhs)
{
lhs += rhs;
return lhs; // copy lhs into return value
}
Widget makeWidget()
{
Widget w;
…
return std::move(w); // DO NOT apply move() on local variable.
// It will hinder RVO.
// If the conditions for the RVO are met, but
// compiler choose not to perform copy elision,
// the object being returned must be treated as
// an rvalue. (std::move is implicitly applied.)
}
78. Avoid overloading on universal references
template<typename T>
void logAndAdd(T&& name) // (1)
{
auto now = std::chrono::system_clock::now();
log(now, “logAndAdd”);
names.emplace(std::forward<T>(name));
}
void logAndAdd(int idx) // (2)
{
auto now = std::chrono::system_clock::now();
log(now, “logAndAdd”);
names.emplace(nameFromIdx(idx));
}
short nameIdx;
logAndAdd(nameIdx); // Error! It will be resolved to logAndAdd(short&& name). Match (1).
// Exactly match (1).
// Promotion to match (2). Function (1) is better than (2).
• Functions taking universal references are the greediest functions in C++.
They instantiate to create matches for almost any type of argument.
79. Avoid overloading on universal references
class Person {
public:
template<typename T>
explicit Person(T&& n) // (1)
: name(std::forward<T>(n)) { … }
explicit Person(int idx) // (2)
: name(nameFromIdx(idx)) { … }
Person(const Person& rhs); // (3) copy ctor (compiler-generated)
Person(Person&& rhs); // (4) move ctor (compiler-generated)
…
private:
std::string name;
};
Person p(“Nancy”);
auto cloneOfP(p); // Error! It will be resolved to Person(Person&). Match (1).
const Person cp(“Nancy”);
auto cloneOfP(cp); // Calls copy ctor. Match (1) and (3).
// Normal function is preferred.
80. Avoid overloading on universal references
class Person {
public:
template<typename T>
explicit Person(T&& n) // (1)
: name(std::forward<T>(n)) { … }
explicit Person(int idx) // (2)
: name(nameFromIdx(idx)) { … }
Person(const Person& rhs); // (3) copy ctor (compiler-generated)
Person(Person&& rhs); // (4) move ctor (compiler-generated)
…
private:
std::string name;
};
class SpecialPerson : public Person {
pubic:
SpecialPerson(const SpecialPerson& rhs) // copy ctor
: Person(rhs) { … } // Error. Calls (1), not base class copy ctor (3)
SpecialPerson(SpecialPerson&& rhs) // move ctor
: Person(std::move(rhs)) { … } // Error. Calls (1), not base class move ctor (4)
};
81. Tag Dispatch
template<typename T>
void logAndAdd(T&& name)
{
logAndAddImpl(
std::forward<T>(name),
std::is_integral<typename std::remove_reference<T>::type>() // remove reference before
// pass to is_integral<T>()
);
}
template<typename T>
void logAndAddImpl(T&& name, std::false_type)
{
auto now = std::chrono::system_clock::now();
log(now, “logAndAdd”);
names.emplace(std::forward<T>(name));
}
void logAndAddImpl(int idx, std::true_type)
{
logAndAdd(nameFromIdx(idx));
}
82. std::enable_if
class Person {
public:
// syntax: enable_if<condition>::type
// is_base_of<T1, T2>::value is true if T2 is derived from T1
// std::decay<T>::type removes reference and cv-qualifiers(i.e., const or volatile) from T
template<
typename T,
typename = typename std::enable_if<
!std::is_base_of<Person,
typename std::decay<T>::type
>::value
&&
!std::is_integral<typename std::remove_reference<T>::type
>::value
>::type
>
explicit Person(T&& n); // universal reference constructor
…
};
• std::enable_if gives you a way to force compilers to behave as if a
particular template didn’t exist.
83. Reference collapsing
When a lvalue is passed as an argument, T is deduced to be an lvalue reference.
When an rvalue is passed, T is deduced to be a non-reference.
template<typename T>
void func(T&& param);
Widget widgetFactory(); // function returning rvalue
Widget w; // a variable (an lvalue)
func(w); // T deduced to be Widget&.
// func(Widget& && param) => func(Widget& param)
func(widgetFactory()); // T deduced to be Widget
• Reference to reference is illegal.
• Compiler may produce reference to reference in particular contexts.
• If either reference is an lvalue reference, the result is an lvalue reference.
Otherwise the result is an rvalue reference.
• A universal reference isn’t a new kind of reference, it’s actually an rvalue
reference in a context where two conditions are satisfied:
• Type deduction distinguishes lvalues from rvalues. Lvalues of type T
are deduced to have type T&, while rvalues of type T yield T as their
deduced type.
• Reference collapsing occurs.
84. Perfect forwarding failure cases
template<typename T>
void fwd(T&& param)
{
f(std::forward<T>(param));
}
// variadic form
template<typename… Ts>
void fwd(Ts&&… params)
{
f(std::forward<Ts>(params)…);
}
f(expression); // if this does one thing,
fwd(expression); // but this does something else, fwd fails
// to perfectly forward expression to f
• We want the forwarded-to function to be able to work with the
originally-passed-in objects.
• Perfect forwarding fails when either of the following occurs:
• Compilers are unable to deduce a type for one or more of
fwd’s parameters
• Compilers deduce the “wrong” type for one or more of fwd’s
parameters
85. Perfect forwarding failure cases
braced initializers
void f(const std::vector<int>& v); // declaration
f({1, 2, 3}); // fine, “{1, 2, 3}” implicitly converted to std::vector<int>
fwd({1, 2, 3}); // error! Compilers are forbidden from deducing a type for the
// expression {1, 2, 3} in the call to fwd, because fwd’s parameter
// isn’t declared to be a std::initializer_list.
// workaround
auto il = {1, 2, 3}; // il’s type deduced to be std::initializer_list<int>
fwd(il);
86. Perfect forwarding failure cases
Declaration-only integral static const data member
class Widget {
public:
static const std::size_t MinVals = 28; // Declaration-only, no memory allocated for it.
…
};
std::vector<int> widgetData;
widgetData.reserve(Widget::MinVals); // Compilers will do const propagation.
size_t* pt = &Widget::MinVals; // Link errors. Pointers need memory to address.
f(Widget::MinVals); // Fine, treated as f(28)
fwd(Widget::MinVals); // Link errors. Reference is like pointer. It needs a location to address.
// workaround
const std::size_t Widget::MinVals; // Give a definition in Widget’s .cpp file.
87. Perfect forwarding failure cases
Overloaded function names and template names
int processVal(int value);
int processVal(int value, int priority);
f(processVal); // Fine
fwd(processVal); // Error! There is no type information on processVal. No type deduction.
template<typename T>
T workOnVal(T param)
{
…
}
fwd(workOnVal); // Error! Which workOnVal instantiation? workOnVal represents many functions.
// workaround
using ProcessFuncType = int (*)(int);
ProcessFuncType processValPtr = processVal; // Use a variable to select which function you want.
fwd(processValPtr); // Fine. processValPtr has type information. It could do type deduction.
fwd(static_cast<ProcessFuncType>(workOnVal)); // Fine. Select workOnVal<int> as parameter.
88. Perfect forwarding failure cases
Bitfields
struct IPv4Header {
std::uint32_t version:4,
IHL:4,
DSCP:6,
ECN:2,
totalLength:16,
…
};
void f(std::size_t sz);
IPv4Header h;
f(h.totalLength); // Fine.
fwd(h.totalLength); // Error. There’s no way to bind a reference to arbitrary bits.
// workaround
auto length = static_cast<std::uint16_t>(h.totalLength); // Make a copy.
fwd(length); // pass the copy to fwd