DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Cutting-Edge Object Detection for Autonomous Vehicles: Advanced Transformers and Multi-Sensor Fusion
  • Writing DTOs With Java8, Lombok, and Java14+
  • Graph API for Entra ID (Azure AD) Object Management
  • A Comprehensive Guide to IAM in Object Storage

Trending

  • Concourse CI/CD Pipeline: Webhook Triggers
  • Breaking Bottlenecks: Applying the Theory of Constraints to Software Development
  • Hybrid Cloud vs Multi-Cloud: Choosing the Right Strategy for AI Scalability and Security
  • AI’s Role in Everyday Development
  1. DZone
  2. Coding
  3. Languages
  4. In-Place Construction for std::any, std::variant, and std::optional

In-Place Construction for std::any, std::variant, and std::optional

A tutorial on how to use these wrappers that were released with C++ 17, and what they can help you with in your code. Read on to get started!

By 
Bartłomiej Filipek user avatar
Bartłomiej Filipek
·
Aug. 16, 18 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
15.1K Views

Join the DZone community and get the full member experience.

Join For Free

When you read articles or reference pages for std::any, std::optional, or std::variant you might notice a few helper types called in_place_* available in constructors.


The article comes from bfilipek.com - blog about C++, new articles each Monday.


Why do we need such syntax? Is this more efficient than the "standard" construction?

Intro

We have the following in_place helper types:

  • std::in_place_t type and a global value std::in_place - used for std::optional
  • std::in_place_type_t type and a global value std::in_place_type - used for std::variant and std::any
  • std::in_place_index_t type and a global value std::in_place_index - used for std::variant

The helpers are used to efficiently initialize objects "in-place" - without additional temporary copy or move operations.

Let's see how those helpers are used.

In std::optional

For a start let's have a look at std::optional. It's a wrapper type, so you should be able to create optional objects almost in the same way as the wrapped object. And in most cases you can:

std::optional<std::string> ostr{"Hello World"};
std::optional<int> oi{10};

You can write the above code without stating the constructor like:

std::optional<std::string> ostr{std::string{"Hello World"}};
std::optional<int> oi{int{10}};

Because std::optional has a constructor that takes U&& (r-value reference to a type that converts to the type stored in the optional). In our case, it's recognised as const char* and strings can be initialized from this.

So what's the advantage of using std::in_place_t in std::optional?

We have at least two points:

  • Default constructor.
  • Efficient construction for constructors with many arguments.

Default Construction

If you have a class with a default constructor, like:

class UserName
{
public:
    UserName() : mName("Default")
    { 

    }
    // ...
};

How would you create an optional that contains UserName{}?

You can write:

std::optional<UserName> u0; // empty optional
std::optional<UserName> u1{}; // also empty

// optional with default constructed object:
std::optional<UserName> u2{UserName()};

That works but it creates an additional temporary object. Here's the output if you run the above code:

UserName::UserName('Default')
UserName::UserName(move 'Default')  // move temp object
UserName::~UserName('')             // delete the temp object
UserName::~UserName('Default')

The code creates a temporary object and then moves it into the object stored in optional.

Here we can use more efficient constructor - by leveraging std::in_place_t:

std::optional<UserName> opt{std::in_place};

Produces the output:

UserName::UserName('Default')
UserName::~UserName('Default')

The object stored in the optional is created in place, in the same way as you'd call UserName{}. No additional copy or move is needed.

You can play with those examples here @Coliru.

Non-Copyable/Movable Types

As you saw in the example from the previous section, if you use a temporary object to initialize the contained value inside std::optional then the compiler will have to use move or copy construction.

But what if your type doesn't allow that? For example std::mutex is not movable or copyable.

In that case std::in_place is the only way to work with such types.

Constructors With Many Arguments

Another use case is a situation where your type has more arguments in a constructor. By default optional can work with a single argument (r-value ref), and efficiently pass it to the wrapped type. But what if you'd like to initialize std::complex(double, double) or std::vector?

You can always create a temporary copy and then pass it in the construction:

// vector with 4 1's:
std::optional<std::vector<int>> opt{std::vector<int>{4, 1}};

// complex type:
std::optional<std::complex<double>> opt2{std::complex<double>{0, 1}};

or use in_place and the version of the constructor that handles variable argument list:

template< class... Args >
constexpr explicit optional( std::in_place_t, Args&&... args );

// or initializer_list:

template< class U, class... Args >
constexpr explicit optional( std::in_place_t,
                             std::initializer_list<U> ilist,
                             Args&&... args );
std::optional<std::vector<int>> opt{std::in_place_t, 4, 1};
std::optional<std::complex<double>> opt2{std::in_place_t, 0, 1};

The second option is quite verbose and omits to create temporary objects. Temporaries, especially for containers or larger objects, are not as efficient as constructing in place.

emplace() Method

If you want to change the stored value inside optional then you can use assignment operator or call emplace().

Following the concepts introduced in C++11 (emplace methods for containers), you have a way to efficiently create (and destroy the old value of, if needed) a new object.

std::make_optional()

If you don't like std::in_place then you can look at make_optional factory function.

The code

auto opt = std::make_optional<UserName>();

auto opt = std::make_optional<std::vector<int>>(4, 1);

Is as efficient as

std::optional<UserName> opt{std::in_place};

std::optional<std::vector<int>> opt{std::in_place_t, 4, 1};

make_optional implement in place construction equivalent to:

return std::optional<T>(std::in_place, std::forward<Args>(args)...);

And also thanks to mandatory copy elision from C++17 there is no temporary object involved.

More

std::optional has 8 versions of constructors! So if you're brave you can analyze them @cppreference - std::optional constructor.

In std::variant

std::variant has two in_place helpers that you can use:

  • std::in_place_type - used to specify which type you want to change/set in the variant.
  • std::in_place_index - used to specify which index you want to change/set. Types are numerated from 0.

Fortunately, you don't always have to use the helpers to create a variant. It's smart enough to recognize if it can be constructed from the passed single parameter:

// this constructs the second/float:
std::variant<int, float, std::string> intFloatString { 10.5f };

For variants, we need the helpers for at least two cases:

  • ambiguity - to distinguish which type should be created where several could match.
  • efficient complex type creation (similar to optional).

Note: by default, the variant is initialized with the first type - assuming it has a default constructor. If the default constructor is not available, then you'll get a compiler error. This is different from std::optional which is initialized to an empty optional - as mentioned in the previous section.

Ambiguity

What if you have initialization like:

std::variant<int, float> intFloat { 10.5 }; // conversion from double?

The value 10.5 could be converted to int or float so the compiler will report a few pages of template errors... but basically, it cannot deduce what type should double be converted to.

But you can easily handle such error by specifying which type you'd like to create:

std::variant<int, float> intFloat { std::in_place_index<0>, 10.5f };

// or

std::variant<int, float> intFloat { std::in_place_type<int>, 10.5f };

Complex Types

Similarly to std::optional if you want to efficiently create objects that get several constructor arguments - the just use std::in_place*:

For example:

std::variant<std::vector<int>, std::string> vecStr { 
    std::in_place_index<0>, { 0, 1, 2, 3 } // initializer list passed into vector
};

More

std::variant has 8 versions of constructors! So if you're brave you can analyze them @cppreference - std::variant constructor.

In std::any 

Following the style of two previous types, std::any can use std::in_place_type to efficiently create objects in place.

Complex Types

In the below example a temporary object will be needed:

std::any a{UserName{"hello"}};

but with:

std::any a{std::in_place_type<UserName>,"hello"};

The object is created in place with the given set of arguments.

std::make_any

For convenience std::any has a factory function called std::make_any that returns the following: 

return std::any(std::in_place_type<T>, std::forward<Args>(args)...);

So in the previous example we could also write:

auto a = std::make_any<UserName>{"hello"};

make_any is probably more straightforward to use.

More

std::any has only 6 versions of constructors (so not 8 as variant/optional). If you're brave you can analyze them @cppreference - std::any constructor.

Summary

With C++11 programmers got a new technique to initialise objects "in place" (see all .emplace() methods for containers) - this avoids unnecessary temporary copies and also allows to work with non-movable/non-copyable types.

With C++17, we got several wrapper types - std::any, std::optional, std::variant - that also allows you to create objects in place efficiently.

If you want the full efficiency of the types, it's probably a good idea to learn how to use std::in_place* helpers or call make_any or make_optional to have equivalent results.

As a reference to this topic, see a recent Jason Turner's video in his C++ Weekly channel. You can watch it here.

More from the author:

Bartek recently published a book - "C++17 In Detail" - have a look if you're interested in the latest C++ Standard.

Object (computer science)

Published at DZone with permission of Bartłomiej Filipek, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Cutting-Edge Object Detection for Autonomous Vehicles: Advanced Transformers and Multi-Sensor Fusion
  • Writing DTOs With Java8, Lombok, and Java14+
  • Graph API for Entra ID (Azure AD) Object Management
  • A Comprehensive Guide to IAM in Object Storage

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: