Sign In Start Free Trial
Account

Add to playlist

Create a Playlist

Modal Close icon
You need to login to use this feature.
  • Book Overview & Buying C++ STL Cookbook
  • Table Of Contents Toc
C++ STL Cookbook

C++ STL Cookbook - Second Edition

By : Bill Weinman
close
close
C++ STL Cookbook

C++ STL Cookbook

By: Bill Weinman

Overview of this book

C++ STL Cookbook is a comprehensive guide that provides practical solutions for mastering the latest features of the C++23 Standard Template Library (STL) through hands-on recipes. Beginning with new features in C++23, this book will help you understand the language's updated mechanics and library features, and offer insights into how they work. Unlike other books, this cookbook takes an implementation-specific, problem-solution approach that will help you overcome hurdles quickly. You'll learn core STL concepts, such as containers, algorithms, utility classes, lambda expressions, iterators, and more, through specific real-world recipes. Building on the success of the first edition, this updated guide includes a new chapter dedicated to the latest features introduced in C++23, such as improved modules, refined ranges, and coroutine-based generators. It also covers essential best practices for writing cleaner and more efficient code, including the use of coroutines, structured bindings, and std::span. Whether you're looking to deepen your understanding of the C++ STL or implement the latest features in your projects, this book provides valuable insights, clear and concise explanations and practical solutions to enhance your C++ programming skills.
Table of Contents (15 chapters)
close
close
14
Index

Use mdspan as a multidimensional array

Defined in C++23, the new mdspan class provides a multidimensional view of any contiguous container, including primitive C arrays.

The mdspan class uses the new C++23 multidimensional extension to the subscript operator [] to provide access to individual elements in a multidimensional space.

The multidimensional space is an abstraction superimposed over a contiguous container or primitive array.

Consider this primitive array:

Figure 1.1: A primitive array suitable for use with mdspan

Figure 1.1: A primitive array suitable for use with mdspan

This array is a simple one-dimensional vector of integers, increasing in value from 1 to 8. Each element in the array is accessed using a scalar index, from [0] for the first element to [7] for the eighth element.

Using mdspan, we can view this exact same data as a two-dimensional array:

mdspan m(array, 2, 4);

Now we can access this same data using two-dimensional subscripts:

Figure 1.2: A two-dimensional view using mdspan

Figure 1.2: A two-dimensional view using mdspan

This gives us a two-dimensional view of our array. The first dimension exposes elements [0] to [3] as m[0,0] to m[0,3], and the second exposes elements [4] to [7] as m[1,0] to m[1,3].

The mdspan class is defined in the <mdspan> header.

Compiler support

As of late 2025, mdspan is supported by the Apple clang and Microsoft Visual C++ compilers. The code in this section has been tested on Apple clang 17.0 and MSVC 19.44.

How to do it

The mdspan class requires a contiguous data set, such as a primitive array, for its source data. STL containers that conform to this requirement include array and vector.

  • For this example, we'll use an STL vector object as our source data:
    vector v {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

    We can access the underlying data of a vector using its data() method. This provides the contiguous data required by mdspan:

    mdspan mds2(v.data(), 2, 6);

    The mdspan object is named mds2 to indicate that it's a two-dimensional view. The first argument is a pointer to the contiguous array, as returned by the data() method of the vector object. The subsequent arguments are the extents of the view dimensions. Each extent specifies the number of elements in that dimension.

    In this example, we have two dimensions with extents of 2 and 6. The product of the extents must not exceed the number of underlying elements. Our source vector has 12 elements and the product of 2 × 6 is 12, so the product of our extents and the size of our source data are equal.

    This creates a two-dimensional mdspan object, with 2 elements in one dimension, and each of those elements has its own span of 6 elements.

  • We can now view our data in two dimensions using the new multidimensional subscript operator:
    for (size_t i1 {0}; i1 < mds2.extent(0); ++i1) {
        for (size_t i2 {0}; i2 < mds2.extent(1); ++i2) {
            print("[{},{}] {:02d} ", i1, i2, mds2[i1,i2]);
        }
        println("");
    }

    The output looks like this:

    [0,0] 01 [0,1] 02 [0,2] 03 [0,3] 04 [0,4] 05 [0,5] 06
    [1,0] 07 [1,1] 08 [1,2] 09 [1,3] 10 [1,4] 11 [1,5] 12

    We format the output to include the subscripts so we can clearly see the position of each element in the two-dimensional array. Each element's integer value is formatted with leading zeros, using the formatter specification {:02d}, so the values align visually.

  • We can view this same data set in three dimensions by simply creating another mdspan object:
    mdspan mds3(v.data(), 2, 3, 2);

    For this object, We pass the same data and specify three extents. This creates a new three-dimensional view into the same source data.

  • We can print the mds3 view using the same technique we used for two dimensions:
    for (size_t i1 {0}; i1 < mds3.extent(0); ++i1) {
        for (size_t i2 {0}; i2 < mds3.extent(1); ++i2) {
            for (size_t i3 {0}; i3 < mds3.extent(2); ++i3) {
                print("[{},{},{}] {:03d} ",
                      i1, i2, i3, mds3[i1,i2,i3]);
            }
            println("");
        }
    }

    That gives us this output:

    [0,0,0] 001 [0,0,1] 002
    [0,1,0] 003 [0,1,1] 004
    [0,2,0] 005 [0,2,1] 006
    [1,0,0] 007 [1,0,1] 008
    [1,1,0] 009 [1,1,1] 010
    [1,2,0] 011 [1,2,1] 012

    Both views may coexist because the underlying data remains in its original location. The mdspan class merely provides a view into that data set.

How it works

The constructor for the mdspan class looks like this:

template<class... OtherIndexTypes>
mdspan(data_handle_type p, OtherIndexTypes... exts);

The first argument is a pointer to a contiguous data source. The exts argument(s) is (are) variadic template argument(s) that take one or more extents, which specify the dimensions of the mdspan object.

The C++23 specification provides new multidimensional support for the subscript [] operator. This means that we may now use comma-separated values in the subscript operator.

The multidimensional subscript operator uses a variadic template to pass arguments as a parameter pack. We can demonstrate this capability with a simplified example of a class that overloads a multidimensional subscript operator:

template <typename T>
struct multidim {
    template <typename... I>
    const T operator[](I... indices) const {
        return (indices + ...);
    }
};

This example uses a fold expression to return the sum of the indices passed to the subscript [] operator. We can call it like this:

multidim<int> m1 {};
println("multidim[1,1,1,1,1]: {}", m1[1,1,1,1,1]);
println("multidim[5,6,7]: {}", m1[5,6,7]);

And we get this output:

multidim[1,1,1,1,1]: 5
multidim[5,6,7]: 18

The mdspan class uses this new feature to provide support for multiple indices into the data, like this:

x = o[1,2,3];

This allows the mdspan object to use the subscript operator to access values in multiple dimensions.

There's more…

Because the mdspan class uses an external data store and does not own the underlying data itself, it returns data elements as references. This allows you to both read and write those elements.

That means that if we start with a simple ordinal data set:

vector v {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

We can modify that data set like this:

for (size_t i1 {0}; i1 < mds2.extent(0); ++i1) {
    for (size_t i2 {0}; i2 < mds2.extent(1); ++i2) {
        mds2[i1, i2] = mds2[i1, i2] * 10 + (int)i2;
    }
}

That modifies the data in the underlying vector object, so when we look at it:

for (auto n : v) {
    print("{} ", n);
}

We now get this output:

10 21 32 43 54 65 70 81 92 103 114 125

While it's important to understand that this allows the data to be mutable, it's not always a good idea. If you want to minimize the risk of side effects, you should qualify your source data as const:

const vector v {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

It's generally good practice to const-qualify any variables that you don't need to modify.

CONTINUE READING
83
Tech Concepts
36
Programming languages
73
Tech Tools
Icon Unlimited access to the largest independent learning library in tech of over 8,000 expert-authored tech books and videos.
Icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Icon 50+ new titles added per month and exclusive early access to books as they are being written.
C++ STL Cookbook
notes
bookmark Notes and Bookmarks search Search in title playlist Add to playlist font-size Font size

Change the font size

margin-width Margin width

Change margin width

day-mode Day/Sepia/Night Modes

Change background colour

Close icon Search
Country selected

Close icon Your notes and bookmarks

Confirmation

Modal Close icon
claim successful

Buy this book with your credits?

Modal Close icon
Are you sure you want to buy this book with one of your credits?
Close
YES, BUY

Submit Your Feedback

Modal Close icon
Modal Close icon
Modal Close icon