thisPointer

Programming Tutorials

Copying and Moving unique_ptr in C++

In C++, a unique_ptr is a smart pointer that owns and manages dynamically allocated memory. It automatically deletes the linked memory when the unique_ptr goes out of scope. One key aspect of unique_ptr is ownership uniqueness, meaning that there can only be one unique_ptr pointing to any given resource at a time. This uniqueness is enforced by the language to prevent multiple unique_ptr s from managing the same resource, which would lead to issues like double deletion.

Can a Unique_ptr be copied?

Since unique_ptr s enforce unique ownership, you cannot copy a unique_ptr . Attempting to do so will result in a compilation error. Here’s a code snippet that shows what happens when you try to copy a unique_ptr :

Moving Unique_ptr is allowed

Although copying a unique_ptr object is not allowed, but you can transfer ownership of underlying memory resource managed by a unique_ptr to another unique_ptr using the std::move() function. This process is called “moving” and effectively renders the source unique_ptr empty (i.e., it becomes nullptr ) and the destination unique_ptr now owns the resource (memory).

Here’s an example of how to move a unique_ptr :

In Modern C++, the Smart Pointer unique_ptr ensures that only one smart pointer can manage a given resource at a time. You cannot copy a unique_ptr but you can transfer its ownership to another unique_ptr using std::move() . This mechanism is important for maintaining resource safety and preventing memory leaks in C++ programs. Remember, once moved, the original unique_ptr will be reset to nullptr , and attempting to use it will be as if it points to no resource at all.

Related posts:

  • Reset unique_ptr in Modern C++
  • What is shared_ptr in C++?
  • Shared_ptr & Custom Deleter in Modern C++
  • Smart Pointer vs Raw Pointer in C++
  • How not to use Smart Pointers in C++?
  • What is weak_ptr in Modern C++ & why do we need it?
  • What is unique_ptr in C++?
  • Introduction to Smart Pointers in Modern C++
  • Using std::find & std::find_if with User Defined Classes
  • C++11 Multithreading – Part 4: Data Sharing and Race Conditions
  • C++11 Multithreading – Part 5: Using mutex to fix Race Conditions
  • C++ Set example and Tutorial – Part 1
  • C++11 Multithreading – Part 7: Condition Variables Explained
  • C++11 Multithreading – Part 8: std::future , std::promise and Returning values from Thread
  • Lambda Functions in C++
  • std::bind in C++ – Explained with Examples
  • The auto Keyword in C++11
  • multimap Example and Tutorial in C++
  • C++11 Multithreading – Part 9: std::async Tutorial & Example
  • How to Capture Member Variables in Lambda function in C++?

Share your love

Leave a comment cancel reply.

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed .

22.5 — std::unique_ptr

22.5 — std::unique_ptr

The compiler is given a lot of flexibility in terms of how it handles this call. It could create a new T, then call function_that_can_throw_exception(), then create the std::unique_ptr that manages the dynamically allocated T. If function_that_can_throw_exception() throws an exception, then the T that was allocated will not be deallocated, because the smart pointer to do the deallocation hasn’t been created yet. This leads to T being leaked.

In the above code, createResource() returns a std::unique_ptr by value. If this value is not assigned to anything, the temporary return value will go out of scope and the Resource will be cleaned up. If it is assigned (as shown in main()), in C++14 or earlier, move semantics will be employed to transfer the Resource from the return value to the object assigned to (in the above example, ptr), and in C++17 or newer, the return will be elided. This makes returning a resource by std::unique_ptr much safer than returning raw pointers!

This browser is no longer supported.

Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.

How to: Create and use unique_ptr instances

  • 8 contributors

A unique_ptr does not share its pointer. It cannot be copied to another unique_ptr , passed by value to a function, or used in any C++ Standard Library algorithm that requires copies to be made. A unique_ptr can only be moved. This means that the ownership of the memory resource is transferred to another unique_ptr and the original unique_ptr no longer owns it. We recommend that you restrict an object to one owner, because multiple ownership adds complexity to the program logic. Therefore, when you need a smart pointer for a plain C++ object, use unique_ptr , and when you construct a unique_ptr , use the make_unique helper function.

The following diagram illustrates the transfer of ownership between two unique_ptr instances.

Diagram that shows moving the ownership of a unique pointer.

unique_ptr is defined in the <memory> header in the C++ Standard Library. It is exactly as efficient as a raw pointer and can be used in C++ Standard Library containers. The addition of unique_ptr instances to C++ Standard Library containers is efficient because the move constructor of the unique_ptr eliminates the need for a copy operation.

The following example shows how to create unique_ptr instances and pass them between functions.

These examples demonstrate this basic characteristic of unique_ptr : it can be moved, but not copied. "Moving" transfers ownership to a new unique_ptr and resets the old unique_ptr .

The following example shows how to create unique_ptr instances and use them in a vector.

In the range for loop, notice that the unique_ptr is passed by reference. If you try to pass by value here, the compiler will throw an error because the unique_ptr copy constructor is deleted.

The following example shows how to initialize a unique_ptr that is a class member.

You can use make_unique to create a unique_ptr to an array, but you cannot use make_unique to initialize the array elements.

For more examples, see make_unique .

Smart Pointers (Modern C++) make_unique

Was this page helpful?

Coming soon: Throughout 2024 we will be phasing out GitHub Issues as the feedback mechanism for content and replacing it with a new feedback system. For more information see: https://aka.ms/ContentUserFeedback .

Submit and view feedback for

Additional resources

std::unique_ptr

Defined in header <memory> .

Declarations ​

Description ​.

std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope.

The object is disposed of, using the associated deleter when either of the following happens:

the managing unique_ptr object is destroyed

the managing unique_ptr object is assigned another pointer via operator = or reset()

The object is disposed of, using a potentially user-supplied deleter by calling get_deleter()(ptr) . The default deleter uses the delete operator, which destroys the object and deallocates the memory.

A unique_ptr may alternatively own no object, in which case it is called empty .

There are two versions of std::unique_ptr :

  • Manages a single object (e.g. allocated with new );
  • Manages a dynamically-allocated array of objects (e.g. allocated with new[] ).

The class satisfies the requirements of MoveConstructible and MoveAssignable, but of neither CopyConstructible nor CopyAssignable.

Type requirements ​

Deleter must be FunctionObject or lvalue reference to a FunctionObject or lvalue reference to function, callable with an argument of type unique_ptr<T, Deleter>::pointer .

Only non-const unique_ptr can transfer the ownership of the managed object to another unique_ptr . If an object's lifetime is managed by a const std::unique_ptr , it is limited to the scope in which the pointer was created.

std::unique_ptr is commonly used to manage the lifetime of objects, including:

  • Providing exception safety to classes and functions that handle objects with dynamic lifetime, by guaranteeing deletion on both normal exit and exit through exception;
  • Passing ownership of uniquely-owned objects with dynamic lifetime into functions;
  • Acquiring ownership of uniquely-owned objects with dynamic lifetime from functions;
  • As the element type in move-aware containers, such as std::vector , which hold pointers to dynamically-allocated objects (e.g. if polymorphic behavior is desired).

std::unique_ptr may be constructed for an incomplete type T , such as to facilitate the use as a handle in the pImpl idiom. If the default deleter is used, T must be complete at the point in code where the deleter is invoked, which happens in the destructor, move assignment operator, and reset member function of std::unique_ptr . (Conversely, std::shared_ptr can't be constructed from a raw pointer to incomplete type, but can be destroyed where T is incomplete). Note that if T is a class template specialization, use of unique_ptr as an operand, e.g. !p requires T 's parameters to be complete due to ADL.

If T is a derived class of some base B , then std::unique_ptr<T> is implicitly convertible to std::unique_ptr<B> . The default deleter of the resulting std::unique_ptr<B> will use operator delete for B , leading to undefined behavior unless the destructor of B is virtual. Note that std::shared_ptr behaves differently: std::shared_ptr<B> will use the operator delete for the type T and the owned object will be deleted correctly even if the destructor of B is not virtual.

Unlike std::shared_ptr , std::unique_ptr may manage an object through any custom handle type that satisfies NullablePointer. This allows, for example, managing objects located in shared memory, by supplying a Deleter that defines typedef boost::offset_ptr pointer ; or another fancy pointer.

Feature-test macroValueStdComment
__cpp_lib_constexpr_memory202202L(C++23)constexpr std::unique_ptr

Member types ​

pubpointerstd::remove_reference<Deleter>::type::pointer if that type exists, otherwise T*. Must satisfy NullablePointer
pubelement_typeT, the type of the object managed by this unique_ptr
pubdeleter_typeDeleter, the function object or lvalue reference to function or to function object, to be called from the destructor

Member functions ​

pub constructs a new unique_ptr
pub destructs the managed object if such is present
pub assigns the unique_ptr

Modifiers ​

pub returns a pointer to the managed object and releases the ownership
pub replaces the managed object
pub swaps the managed objects

Observers ​

pub returns a pointer to the managed object
pub returns the deleter that is used for destruction of the managed object
pub checks if there is an associated managed object

Single-object version, unique_ptr<T> ​

pub dereferences pointer to the managed object

Array version, unique_ptr<T[]> ​

pub provides indexed access to the managed array

Non-member functions ​

pub
make_unique_for_overwrite
creates a unique pointer that manages a new object
pub
operator<
operator<=
operator>
operator>=
operator<=>
compares to another unique_ptr or with nullptr
pub outputs the value of the managed pointer to an output stream
pub specializes the std::swap algorithm

Helper Classes ​

pub hash support for std::unique_ptr
  • Declarations
  • Description
  • Member types
  • Member functions
  • Non-member functions
  • Helper Classes
  • <cassert> (assert.h)
  • <cctype> (ctype.h)
  • <cerrno> (errno.h)
  • C++11 <cfenv> (fenv.h)
  • <cfloat> (float.h)
  • C++11 <cinttypes> (inttypes.h)
  • <ciso646> (iso646.h)
  • <climits> (limits.h)
  • <clocale> (locale.h)
  • <cmath> (math.h)
  • <csetjmp> (setjmp.h)
  • <csignal> (signal.h)
  • <cstdarg> (stdarg.h)
  • C++11 <cstdbool> (stdbool.h)
  • <cstddef> (stddef.h)
  • C++11 <cstdint> (stdint.h)
  • <cstdio> (stdio.h)
  • <cstdlib> (stdlib.h)
  • <cstring> (string.h)
  • C++11 <ctgmath> (tgmath.h)
  • <ctime> (time.h)
  • C++11 <cuchar> (uchar.h)
  • <cwchar> (wchar.h)
  • <cwctype> (wctype.h)

Containers:

  • C++11 <array>
  • <deque>
  • C++11 <forward_list>
  • <list>
  • <map>
  • <queue>
  • <set>
  • <stack>
  • C++11 <unordered_map>
  • C++11 <unordered_set>
  • <vector>

Input/Output:

  • <fstream>
  • <iomanip>
  • <ios>
  • <iosfwd>
  • <iostream>
  • <istream>
  • <ostream>
  • <sstream>
  • <streambuf>

Multi-threading:

  • C++11 <atomic>
  • C++11 <condition_variable>
  • C++11 <future>
  • C++11 <mutex>
  • C++11 <thread>
  • <algorithm>
  • <bitset>
  • C++11 <chrono>
  • C++11 <codecvt>
  • <complex>
  • <exception>
  • <functional>
  • C++11 <initializer_list>
  • <iterator>
  • <limits>
  • <locale>
  • <memory>
  • <new>
  • <numeric>
  • C++11 <random>
  • C++11 <ratio>
  • C++11 <regex>
  • <stdexcept>
  • <string>
  • C++11 <system_error>
  • C++11 <tuple>
  • C++11 <type_traits>
  • C++11 <typeindex>
  • <typeinfo>
  • <utility>
  • <valarray>
  • C++11 allocator_arg_t
  • C++11 allocator_traits
  • auto_ptr_ref
  • C++11 bad_weak_ptr
  • C++11 default_delete
  • C++11 enable_shared_from_this
  • C++11 owner_less
  • C++11 pointer_traits
  • raw_storage_iterator
  • C++11 shared_ptr
  • C++11 unique_ptr
  • C++11 uses_allocator
  • C++11 weak_ptr

enum classes

  • C++11 pointer_safety
  • C++11 addressof
  • C++11 align
  • C++11 allocate_shared
  • C++11 const_pointer_cast
  • C++11 declare_no_pointers
  • C++11 declare_reachable
  • C++11 dynamic_pointer_cast
  • C++11 get_deleter
  • C++11 get_pointer_safety
  • get_temporary_buffer
  • C++11 make_shared
  • return_temporary_buffer
  • C++11 static_pointer_cast
  • C++11 undeclare_no_pointers
  • C++11 undeclare_reachable
  • uninitialized_copy
  • C++11 uninitialized_copy_n
  • uninitialized_fill
  • uninitialized_fill_n
  • C++11 allocator_arg
  • C++11 unique_ptr::~unique_ptr
  • C++11 unique_ptr::unique_ptr

member functions

  • C++11 unique_ptr::get
  • C++11 unique_ptr::get_deleter
  • C++11 unique_ptr::operator bool
  • C++11 /" title="unique_ptr::operator->"> unique_ptr::operator->
  • C++11 unique_ptr::operator[]
  • C++11 unique_ptr::operator*
  • C++11 unique_ptr::operator=
  • C++11 unique_ptr::release
  • C++11 unique_ptr::reset
  • C++11 unique_ptr::swap

non-member overloads

  • C++11 relational operators (unique_ptr)
  • C++11 swap (unique_ptr)

std:: unique_ptr ::operator=

move assignment (1)
assign null pointer (2)
type-cast assignment (3)
copy assignment (deleted!) (4)
  • lhs owns the data previously owned by rhs and has rhs 's former stored pointer and stored deleter .
  • The data owned by lhs before the call has been deleted (using the deleter that member get_deleter would provide before the call).

Return value

main () { std::unique_ptr< > foo; std::unique_ptr< > bar; foo = std::unique_ptr< >( (101)); bar = std::move(foo); std::cout << ; (foo) std::cout << *foo << ; std::cout << ; std::cout << ; (bar) std::cout << *bar << ; std::cout << ; 0; }

Fluent C++

About Jonathan Boccara

Hello, my name is Jonathan Boccara, I'm your host on Fluent C++. I have been a developer for 10 years. My focus is on how to write expressive code . I wrote the book The Legacy Code Programmer's Toolbox . I'm happy to take your feedback, don't hesitate to drop a comment on a post, follow me or get in touch directly !

Jonathan Boccara's blog

Recent Posts

  • Usage First, Implementation After: A Principle of Software Development
  • Design Patterns VS Design Principles: Factory method
  • How to Store an lvalue or an rvalue in the Same Object
  • Copy-Paste Developments
  • Design Patterns VS Design Principles: Abstract Factory
  • How to Generate All the Combinations from Several Collections

std unique_ptr move assignment

How to Transfer unique_ptrs From a Set to Another Set

Daily C++

Transferring a  std::unique_ptr  to another std::unique_ptr is an easy thing to do:

Easy peasy, lemon squeezy.

Now what if those unique_ptr s are living inside of two sets? It should be just as easy to transfer those in the first set over to the second set, right?

It turns out that it’s not easy, neither peasy, and even less lemon squeezy. Unless you have C++17, in which case it’s a breeze. But before C++17, it’s not. Here are various alternatives you can use to approach this.

Let’s see the motivating problem first.

move unique_ptr C++ sets

The case: transfering sets of unique_ptrs

We start by seeing what a std::set of std::unique_ptr  would represent, and then we see what problem happens when trying to transfer the contents of one set to another.

Sets of unique_ptrs: unique and polymorphic

To begin with, you may have wondered why do a unique_ptr on an int as in the above example. Except for showing a simple example, well, it has no use at all.

A more realistic case would be one of runtime polymorphism via inheritance, with a Base  class that can have Derived  classes:

Base Derived

And we would use the base class polymorphically by holding it with some sort of handle (pointer or reference). To encapsulate the memory management , we would use a std::unique_ptr<Base> .

Now if we want a collection of several objects implementing Base , but that could be of any derived classes, we can use a collection of unique_ptr<Base> s .

Finally, we may want to prevent our collection to have duplicates. This is what std::set  does. Note that to implement this constraint, std::set  needs a way to compare its objects together.

Indeed, by declaring a set this way:

the comparison between elements of the set will call the operator<  of std::unique_ptr , which compares the memory addresses of the pointers inside them.

In most cases, this is not what you want. When we think “no duplicates”, it generally means “no logical duplicates” as in: no two elements have the same value. And not “no two elements are located at the same address in memory”.

To implement no logical duplicates, we need to call the operator<  on Base  (provided that it exists, maybe using an id provided by Base for instance) to compare elements and determines whether they are duplicates. And to make the set use this operator, we need to customize the comparator of the set:

To avoid writing this type every time we instantiate such a set in code, we can hide its technical aspects behind an alias:

Transferring unique_ptrs between two sets

Ok. We’re all set (ha-ha) and ready to transfer the elements of a set to another one. Here are our two sets:

To transfer elements efficiently , we use the insert  method:

But this leads to a compilation error!

Indeed, the insert  methods attemps to make a copy of the unique_ptr  elements.

What to do then?

C++17’s new method on set: merge

set s and map s in C++ are internally implemented as trees. This lets them ensure the algorithmic complexities guaranteed by the methods of their interface. Before C++17, it didn’t show in the interface.

C++17 adds the merge  method to sets:

This makes destination   take over the nodes of the tree inside of source . It’s like performing a splicing on lists. So after executing this line, destination  has the elements that source  had, and source  is empty.

And since it’s only the nodes that get modified, and not what’s inside them, the unique_ptr s don’t feel a thing. They are not even moved.

destination  now has the unique_ptr s, end of story.

Now if you don’t have C++17 in production, which is the case of a lot of people at the time I’m writing these lines, what can you do?

We can’t move from a set

The standard algorithm to move elements from a collection to another collection is std::move . Here is how it works with  std::vector :

after the execution of this line, destination  has the elements that source  had and source  is not empty, but has empty unique_ptr s.

Let’s try to do the same thing with our sets now:

We get the same compilation error as in the beginning, some unique_ptr s are getting copied:

This may look surprising. The purpose of the std::move  algorithm is to avoid making copies on the unique_ptr  elements and move them instead, so why are they being copied??

The answer lies in how the set provides access to its elements. When dereferenced, a set’s iterator does not return a unique_ptr& , but rather a const unique_ptr& . It is to make sure that the values inside of the set don’t get modified without the set being aware of it. Indeed, it could break its invariant of being sorted.

So here is what happens:

  • std::move  dereferences the iterator on set and gets a  const unique_ptr& ,
  • it calls std::move  on that references, thus getting a  const unique_ptr&& ,
  • it calls the insert method on the insert output iterator and passes it this  const unique_ptr&& ,
  • the insert  method has two overloads: one that takes a  const unique_ptr& , and one that takes a  unique_ptr&& . Because of the const  in the type we’re passing, the compiler cannot resolve this call to the second method, and calls the first one instead.

Then the insert output iterator calls calls the insert  overload on the set that takes a const unique_ptr&  and in turn calls the copy constructor of unique_ptr  with that l-value reference, and that leads to the compilation error.

Making a sacrifice

So before C++17, moving elements from a set doesn’t seem to be possible. Something has to give: either moving, or the sets. This leads us to two possible aspects to give up on.

Keeping the set but paying up for the copies

To give up on the move and accepting to copy the elements from a set to another, we need to make a copy of the contents pointed by the unique_ptr s.

For this, let’s assume that Base  has is a polymorphic clone implemented by its method  cloneBase , overriden in Derived :

At call site, we can make copies of the unique_ptr s from a set over to the other one, for instance this way:

Or, with a for loop:

Keeping the move and throwing away the set

The set that doesn’t let the move happen is the source  set. If you only need the destination  to have unique elements, you can replace the source  set by a std::vector .

Indeed, std::vector  does not add a const to the value returned by its iterator. We can therefore move its elements from it with the std::move  algorithm:

Then the destination  set contains a unique_ptr that has the contents that used to be in the one of the source , and the source  vector now contains an empty unique_ptr .

Live at head

You can see that there are ways around the problem of transferring unique_ptr s from a set to another one. But the real solution is the merge  method of std::set  in C++17.

The standard library is getter better and better as the language evolves. Let’s do what we can to move (ha-ha) to the latest version of C++, and never look back.

Related articles:

  • Move iterators: where the STL meets move semantics
  • Smart developers use smart pointers
  • The STL learning resource

twitter

Comments are closed

  • C++ Data Types
  • C++ Input/Output
  • C++ Pointers
  • C++ Interview Questions
  • C++ Programs
  • C++ Cheatsheet
  • C++ Projects
  • C++ Exception Handling
  • C++ Memory Management

Unique_ptr in C++

std::unique_ptr is a smart pointer introduced in C++11. It automatically manages the dynamically allocated resources on the heap. Smart pointers are just wrappers around regular old pointers that help you prevent widespread bugs. Namely, forgetting to delete a pointer and causing a memory leak or accidentally deleting a pointer twice or in the wrong way. They can be used in a similar way to standard pointers. They automate some of the manual processes that cause common bugs.

Prerequisites: Pointer in C++ , Smart Pointers in C++.

  • unique_ptr<A>: It specifies the type of the std::unique_ptr. In this case- an object of type A.
  • new A : An object of type A is dynamically allocated on the heap using the new operator.
  • ptr1 : This is the name of the std::unique_ptr variable.

What happens when unique_ptr is used?

When we write unique_ptr<A> ptr1 (new A), memory is allocated on the heap for an instance of datatype A. ptr1 is initialized and points to newly created A object. Here, ptr1 is the only owner of the newly created object A and it manages this object’s lifetime. This means that when ptr1 is reset or goes out of scope, memory is automatically deallocated and A’s object is destroyed.

When to use unique_ptr?

When ownership of resource is required. When we want single or exclusive ownership of a resource, then we should go for unique pointers. Only one unique pointer can point to one resource. So, one unique pointer cannot be copied to another. Also, it facilitates automatic cleanup when dynamically allocated objects go out of scope and helps preventing memory leaks.

Note: We need to use the <memory> header file for using these smart pointers.

Examples of Unique_ptr

Lets create a structure A and it will have a method named printA to display some text. Then in the main section, let’s create a unique pointer that will point to the structure A. So at this point, we have an instance of structure A and p1 holds the pointer to that.

     

Now let’s create another pointer p2 and we will try to copy the pointer p1 using the assignment operator(=).

     

The above code will give compile time error as we cannot assign pointer p2 to p1 in case of unique pointers. We have to use the move semantics for such purpose as shown below.

Managing object of type A using move semantics.

       

Note once the address in pointer p1 is copied to pointer p2, the pointer p1’s address becomes NULL(0) and the address stored by p2 is now the same as the address stored by p1 showing that the address in p1 has been transferred to the pointer p2 using the move semantics.

Please Login to comment...

Similar reads.

  • Geeks Premier League
  • Geeks Premier League 2023

Improve your Coding Skills with Practice

 alt=

What kind of Experience do you want to share?

Stack Exchange Network

Stack Exchange network consists of 183 Q&A communities including Stack Overflow , the largest, most trusted online community for developers to learn, share their knowledge, and build their careers.

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Using std::unique_ptr and std::move

I'm trying out new things (on class Inproc ), specifically using std::unique_ptr and std::move , and I'm wondering if I'm doing it right. The code compiles so that gives me hope. Still, what do you think?

Note: I'm working without exceptions for reasons I won't go into. That means the factory functions are required to handle errors. Keep that in mind please.

  • memory-management
  • factory-method

200_success's user avatar

  • \$\begingroup\$ Would it be possible to have Inproc(Fan &&bound__, Fan &&connected__); as constructor and still use std::unique_ptr and std::move? \$\endgroup\$ –  Goswin von Brederlow Commented Nov 22, 2014 at 18:18
  • \$\begingroup\$ The code is correct as far as move semantics go. I would like you to say what Inproc and Fan are actually supposed to do before reviewing. \$\endgroup\$ –  nwp Commented Nov 22, 2014 at 18:53
  • \$\begingroup\$ @nwp: Fan does fair queueing of incoming messages and round-robin on outgoing messages and Inproc connects 2 fans for in process communication (as opposed to network). \$\endgroup\$ –  Goswin von Brederlow Commented Nov 22, 2014 at 19:14
  • \$\begingroup\$ I'm unsure about having to use std::move() twice. But that is required if Inproc() takes std::unique_ptr<Fan> or std::unique_ptr<Fan>&& . On the other hand if Inproc() takes std::unique_ptr<Fan>& then the std::move in create becomes uneccessary. Is that a more common style? \$\endgroup\$ –  Goswin von Brederlow Commented Nov 22, 2014 at 19:17

4 Answers 4

Programming / detail review.

Fan(Fan *peer__ = NULL); (and some more occurrences)

Identifiers that contain a double underscore are reserved for the compiler/libary ("C++ implementation").

The NULL macro can be thought of as deprecated. Use the nullptr keyword instead. It is more typesafe.

static Fan * create(Fan *peer = NULL);

Since those pointers you return from create own a resource (dynamically allocated memory / an object with dynamic storage duration), you should consider making them smart pointers:

The function parameter can remain a raw pointer, since it does not convey/transfer ownership. One could argue that some dumb pointer wrapper that explicitly states it does not own a resource might be more appropriate.

Inproc(std::unique_ptr<Fan> bound__, std::unique_ptr<Fan> connected__);

In your original version, you used a std::unique_ptr<Fan>&& . Let's examine the alternatives:

Only the first guarantees that the unique_ptr you pass in will be empty afterwards. All others do not, by means of (parameter) types, tell you what happens to your pointer. For more details, see the solution of GotW #105: Smart Pointers, Part 3

If you want to always acquire ownership of the pointer argument, you should define your interface using pass-by-value for unique_ptr s.

Let's see how we can refactor Inproc::create() using these changes:

Note that by returning nullptr in all cases of an error, you lose information about what error occurred.

Design review

private: Fan(Fan *peer__ = NULL);

Derived classes can only copy a Fan , they cannot create a new one. This seems contradictory to the virtual dtor, which seems to provide "support" for inheritance.

From the code shown, it is unclear to me why you need to restrict Fan s to be created via this factory function (is there a pattern name for this?). It certainly decreases reusability of this class.

For Inproc::create() , the situation could even be worse: You cannot test Inproc independently of Fan , since you can't provide some kind of mocked Fan to construct it. Also, it doesn't seem to require the dynamic storage duration of its unique_ptr subobjects; but again, we don't see the whole program here.

nwp's answer reminded me that you might use pointers to indicate an error. I think that is not a good reason to require dynamic storage duration (-> new + pointers). One solution could be a factory function that returns an optional , or that creates objects in-place at a location passed as a parameter. The former can even be used easily for aggregation; neither can easily be used for inheritance. Another option is to introduce an empty / error state, that the object is set to if construction fails. I don't really like the idea of "empty states" for a class, since those weaken the guarantees / invariants it can provide. But it's a common sight to be able to implement e.g. default constructors. Look at std::thread , std::Xstream etc. - they all have default constructors, even though they leave the object in an almost unusable state.

dyp's user avatar

  • \$\begingroup\$ As said I'm trying this out on Inproc. Fan is legacy code. It's also cut down to just the essential stuff used in the example. There is a ton of other functionality that is irelevant to the constructor so I removed that to keep things simple. \$\endgroup\$ –  Goswin von Brederlow Commented Nov 22, 2014 at 23:13

I am still not sure what Fan and Inproc are meant to do. I just fail to see the queuing functionality. Also there are function implementations missing, for example for Fan(Fan *peer__ = NULL); .

The main point of unique_ptr is that it eliminates ownership problems. When you have a unique_ptr you are the only one who has access to an object and do not need to worry about other code. With regular pointers sometimes you must delete them and sometimes you must not, which is confusing. If someone just calls Inproc::create(); it is already a memory leak which unique_ptr s were meant to prevent:

Avoid functions such as create, init, destroy, copy and clone. C++ has special member functions for that: Constructor, destructor and copy assignment. They cannot always be used, but if they can prefer them. Inpoc::create should actually be a regular constructor. If the construction of an Inproc can fail and you cannot use exceptions provide a member function bool is_valid() const that the caller can use to check if construction worked.

Prefer nullptr over NULL because nullptr works correctly with templates whereas NULL will occasionally fail.

Avoid using new and delete . To create a unique_ptr use make_unique and for shared_ptr use make_shared . Somehow the C++11 standard forgot to specify make_unique so if there is no std::make_unique in your STL write your own . You can even write a make_unique_nothrow for your special requirement.

For the questions:

Would it be possible to have Inproc(Fan &&bound__, Fan &&connected__); as constructor and still use std::unique_ptr and std::move ? You can express that:

There is no need for std::move here because *bound.release() is already a temporary object. I screwed that one up. Apparently you do need the move even though * unique_ptr.release() is logically a temporary. Thanks to @dyp for pointing it out. release is completely misplaced, that would create a memory leak. This time I tested it.

... if Inproc() takes std::unique_ptr<Fan>& then the std::move in create becomes uneccessary. Is that a more common style? I dislike unique_ptr & as a parameter. Imagine you want to pass an object that is on the stack. The code bloats and becomes inefficient if you have to do that. Instead pass a regular pointer if the resource is optional and a reference if it is not. Also use const if it makes sense. I would also recommend against std::unique_ptr && , because if is more typing and more mental stress than just std::unique_ptr while the meaning is the same.

Community's user avatar

  • \$\begingroup\$ "The main point of unique_ptr is that it eliminates ownership problems." That is exactly what I want to do. \$\endgroup\$ –  Goswin von Brederlow Commented Nov 22, 2014 at 23:34
  • \$\begingroup\$ "Inpoc::create should actually be a regular constructor." It can't be a normal constructor since I need to be able to signal errors. The existing code is using dependency inversion instead of creating dummy objects with is_good() functions. Changing that would be a major redesign. \$\endgroup\$ –  Goswin von Brederlow Commented Nov 22, 2014 at 23:37
  • \$\begingroup\$ As to std::make_unique . My c++ reference has std::unique_ptr(...) as constexpr so it is safe to use without exceptions. No such garantee on std::make_unique . Plus that needs a constructor which often conflicts with the dependency inversion and create factories in this code. Otherwise I will use it though. \$\endgroup\$ –  Goswin von Brederlow Commented Nov 22, 2014 at 23:42

If you go for std::unique_ptr -- which is a good thing -- I suggest you to do it consistently. That is, your class Fan should rather look like this:

Several things to note here:

Use std::unique_ptr<Fan> peer_ as a member of the class. (or a shared_ptr -- which one depends on your requirements). Do not use a raw pointer . Why? If you copy your class via the compiler-created copy constructor, the pointer is simply copied. Now two pointers point to the same object. If one class goes out of scope, it usually calls delete to destruct the object. But then the other class points to nothing, which is a problem. Otherwise, if you don't call delete , none of the two objects frees the memory an thus you get an instance of the famous memory leak.

This is why smart pointers were created. std::unique_ptr says "that object is mine". Therefore, you can't copy a unique_ptr , and thus the compiler also won't generate a compiler generated copy constructor and assignment operator. This, the above problem is solved trivially, as there never will be two classes which both want to call delete -- there is always a single owner.

On the other hand, std::shared_ptr allows to share the object among many owners. It avoids the above problem by keeping track how many point to the pointed-to object. Only when nobody needs it anymore, it will call delete once.

Thus keep in mind to use raw pointers only when you have a good reason. Such one is, when the pointer is only there to observe the pointed-to object. As such it will never call delete nor will do any other kind of memory management.

Once you settled for one smart pointer, it is often the case that the compiler generated default function are sufficient. Therefore, I set the virtual destructor to the compiler generator default implementation. (Otherwise, if you'd really planned some implementation, you would have to regard the "rule of three").

Return a std::unique_ptr from your factory function. Remember, the one who calls create gets a completely new Fan object. He owns it, and is responsible for its memory management. This is exactly what std::unique_ptr does: it possesses the pointed-to-object and takes it with it into his grave (less dramatically: it calls the destructor or Fan once it goes out of scope).

Next, you should be aware how you call the constructor and also create . In your case, you pass a raw pointer to both. In each case, the unique_ptr<Fan> _peer inside Fan then will take the ownership of the pointed-to object. So don't use something which is already owned (i.e., already managed by a smart pointer). At best you only use it directly with new Fan() , or better with std::make_unique .

Just to note, you should have a good reason why at all you have such a create function. The constructor is often sufficient, and the outside world determines the memory management. Such a create function is often delegated to a factory class.

Use nullptr instead of NULL . nullptr in C++11 is especially designed for this purpose (whereas NULL in seldom encountered cases can lead to errors.

I would step back from using double underscores __ , as they are often used by the compiler. Either use one underscore inside your class, i.e. peer_ or _peer and none in the initialization peer , or make it the other way round.

For the Inproc class, similar things as above hold.

  • 1 \$\begingroup\$ I think it's quite likely Fan::peer_ is an observing pointer: Note how Inproc stores two unique_ptr s, where one is supposed to refer to the other one. \$\endgroup\$ –  dyp Commented Nov 22, 2014 at 21:04
  • \$\begingroup\$ @dyp: I admit I have no idea of the purpose of these two classes. I just reviewed this one class Fan and wrote what came to my mind... \$\endgroup\$ –  davidhigh Commented Nov 22, 2014 at 21:08
  • \$\begingroup\$ @dyp: I guess you're right. The OP expicitly says he's trying new things in the class Inproc . Forgot this during my write-diarrhea. \$\endgroup\$ –  davidhigh Commented Nov 22, 2014 at 21:25

I think I've incorporated all the comments. If I missed something please remind me below.

I changed the Fan class to use std::unique_ptr too and added some code to the skeleton. But let me mention again that it's just a sekelton for some Resource that needs to be moved into Inproc. It doesn't do anything as is.

I changed NULL to nullptr and set the copy constructor/assignment to delete since copying the resources is not allowed. I also added some comment and added debug output and a main() function so you can run it and see what goes on. The first example would compile but couldn't be run.

I couldn't get std::make_unique to work since the constructor is private.

I changed *__ to * init to avoid the reserved namespace. I can't use plain peer since that would shadow the peer() function. So member variables are * and initializers are *_init across the board.

Your Answer

Sign up or log in, post as a guest.

Required, but never shown

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy .

Not the answer you're looking for? Browse other questions tagged c++ memory-management pointers factory-method c++14 or ask your own question .

  • The Overflow Blog
  • Navigating cities of code with Norris Numbers
  • Featured on Meta
  • We've made changes to our Terms of Service & Privacy Policy - July 2024
  • Bringing clarity to status tag usage on meta sites

Hot Network Questions

  • Short story or novella where a man's wife dies and is brought back to life. The process is called rekindling. Rekindled people are very different
  • If A is precise and B is precise then is their conjunction (necessarily) precise?
  • Is "Alice loves candies" actually necessary for "Alice loves all sweet foods"?
  • Did the United States have consent from Texas to cede a piece of land that was part of Texas?
  • How can I cover all my skin (face+neck+body) while swimming outside (sea or outdoor pool) to avoid UV radiations?
  • How do you "stealth" a relativistic superweapon?
  • Power line crossing data lines via the ground plane
  • Does epistemology categorize knowledge into 5 categories, like Justice Peter Gibson did?
  • Why do these finite group Dedekind matrices seem to have integer spectrum when specialized to the order of group elements?
  • Symbol between two columns in a table
  • ambobus? (a morphologically peculiar adjective with a peculiar syntax here)
  • MOSFETs keep shorting way below rated current
  • Unexpected behaviour during implicit conversion in C
  • They come in twos
  • Where exactly was this picture taken?
  • Guitar amplifier placement for live band
  • Is it mandatory in German to use the singular in negative sentences like "none of the books here are on fire?"
  • Discrete cops and robbers
  • Is It Possible to Assign Meaningful Amplitudes to Properties in Quantum Mechanics?
  • Giant War-Marbles of Doom: What's the Biggest possible Spherical Vehicle we could Build?
  • Bootloading Ubuntu 24.04 ISO error SBAT data failed: Security Policy Violation without removing SBAT policy and disabling Secure Boot
  • Using illustrations and comics in dissertations
  • what is wrong with my intuition for the sum of the reciprocals of primes?
  • Age is just a number!

std unique_ptr move assignment

cppreference.com

Std::unique_ptr<t,deleter>:: reset.

(C++20)
(C++20)
(C++11)
(C++20)
(C++17)
(C++11)
(C++11)
(basic types, RTTI)
(C++20)
(C++20)
three_way_comparable_with (C++20)
   
is_ltis_lteq (C++20)(C++20)
is_gtis_gteq (C++20)(C++20)
General utilities
(C++20)
(deprecated in C++20)
rel_ops::operator>
rel_ops::operator>=
cmp_lesscmp_less_than (C++20)(C++20)   
cmp_greatercmp_greater_than (C++20)(C++20)


  
  
(until C++23)
(until C++23)
(until C++23)
(until C++23)
(until C++23)
(until C++23)



)
)
)
start_lifetime_as_array (C++23)

)

unique_ptr::operator->
make_unique_for_overwrite (C++20)
operator!=operator<operator>operator<=operator>=operator<=> (until C++20)(C++20)
reset( pointer ptr = pointer() ) noexcept; (1) (constexpr since C++23)
< class U >
void reset( U ptr ) noexcept;
(2) (constexpr since C++23)
reset( = nullptr ) noexcept; (3) (constexpr since C++23)

Replaces the managed object.

  • Saves a copy of the current pointer old_ptr = current_ptr .
  • Overwrites the current pointer with the argument current_ptr = ptr .
  • If the old pointer was non-empty, deletes the previously managed object if ( old_ptr ) get_deleter ( ) ( old_ptr ) .
  • U is the same type as pointer , or
  • pointer is the same type as element_type* and U is a pointer type V* such that V(*)[] is convertible to element_type(*)[] .
Parameters Return value Notes Example Defect reports See also

[ edit ] Parameters

ptr - pointer to a new object to manage

[ edit ] Return value

[ edit ] notes.

To replace the managed object while supplying a new deleter as well, move assignment operator may be used.

A test for self-reset, i.e. whether ptr points to an object already managed by * this , is not performed, except where provided as a compiler extension or as a debugging assert. Note that code such as p. reset ( p. release ( ) ) does not involve self-reset, only code like p. reset ( p. get ( ) ) does.

[ edit ] Example

[ edit ] defect reports.

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
C++11 rejected qualification conversions accepts

[ edit ] See also

returns a pointer to the managed object and releases the ownership
(public member function)
  • Recent changes
  • Offline version
  • What links here
  • Related changes
  • Upload file
  • Special pages
  • Printable version
  • Permanent link
  • Page information
  • In other languages
  • This page was last modified on 23 June 2023, at 02:55.
  • Privacy policy
  • About cppreference.com
  • Disclaimers

Powered by MediaWiki

Move Constructor & Assignment Operator With std::shared_ptr

In an earlier article , we have seen how move constructor & move assignment operators helped us in creating our own unique_ptr . Here we will use move constructor & assignment operator to implement unsophisticated shared_ptr.

Implementing Our shared_ptr with Move Constructor & Assignment Operator #

  • In some cases, we have a requirement where a single resource is represented by multiple pointers. We can not accomplish this by std::unique_ptr . To accomplish this, we can add a new variable to our smart pointer class which keeps track of reference count at the real-time. And when the reference count goes to zero which means nobody is using that resource, we will deallocate that resource.
  • Unlike std::unique_ptr , which is designed to singly own and manage a resource, std::shared_ptr is meant to solve the case where you need multiple smart pointers co-owning a resource.
<class T> class smart_ptr { T* m_ptr; uint32_t *m_refCount; public: smart_ptr(T* ptr = nullptr):m_ptr(ptr) { if(m_ptr) m_refCount = new uint32_t(1); else m_refCount = nullptr; } ~smart_ptr() { if(m_refCount != nullptr){ (*m_refCount)--; if((*m_refCount) == 0){ delete m_ptr; delete m_refCount; } } } // Copy constructor smart_ptr(const smart_ptr& a) { m_ptr = a.m_ptr; m_refCount = a.m_refCount; (*m_refCount)++; } // Move constructor smart_ptr(smart_ptr&& a): m_ptr(a.m_ptr), m_refCount(a.m_refCount) { a.m_ptr = nullptr; a.m_refCount = nullptr; } // Copy assignment smart_ptr& operator=(const smart_ptr& a) { m_ptr = a.m_ptr; m_refCount = a.m_refCount; (*m_refCount)++; return *this; } // Move assignment smart_ptr& operator=(smart_ptr&& a) { if (&a == this) return *this; delete m_ptr; delete m_refCount; m_ptr = a.m_ptr; a.m_ptr = nullptr; m_refCount = a.m_refCount; a.m_refCount = nullptr; return *this; } T& operator*() const { return *m_ptr; } T* operator->() const { return m_ptr; } }; class Resource { public: Resource() { std::cout << "Resource acquired\n"; } ~Resource() { std::cout << "Resource destroyed\n"; } }; smart_ptr<Resource> func(smart_ptr<Resource> temp) { // Do something return temp; } int main() { Resource *res = new Resource; smart_ptr<Resource> ptr1(res); { smart_ptr<Resource> ptr2(ptr1); auto ptr3 = func(ptr1); std::cout << "Killing one shared pointer\n"; } std::cout << "Killing another shared pointer\n"; return 0; }
  • Unlike std::unique_ptr , which uses a single pointer internally, std::shared_ptr uses two pointers internally. One pointer points at the managed resource. The other points at a “control block”, which is a dynamically allocated object that tracks of a bunch of stuff, including how many std::shared_ptr are pointing at the resource.
  • Here I have only used a single variable to keep track of references pointing to resource for simplicity. The actual implementation is a bit bulky for more feature & security purpose.

A bit about move constructor & move assignment operator #

When does the move constructor & move assignment operator get called?

The move constructor and move assignment are called when those functions have been defined, and the argument for construction or assignment is an r-value . Most typically, this r-value will be a literal or temporary value.

  • In most cases, a move constructor and move assignment operator will not be provided by default, unless the class does not have any defined copy constructors, copy assignment, move assignment, or destructors. However, the default move constructor and move assignment do the same thing as the default copy constructor and copy assignment ( make copies, not do moves ).

l-value reference & r-value reference #

  • I have already written a separate article for that.

std::move #

  • In C++11, std::move is a standard library function that serves a single purpose – to convert its argument into an r-value .
  • Once you start using move semantics more regularly, you’ll start to find cases where you want to invoke move semantics, but the objects you have to work with are l-values , not r-values .

Use case or benefit of std::move #

  • Consider the following swap() function as an example:
<class T> void swap(T& a, T& b) { T tmp { a }; // invokes copy constructor a = b; // invokes copy assignment b = tmp; // invokes copy assignment } int main() { std::string x{ "abc" }; std::string y{ "de" }; swap(x, y); return 0; }
  • Above swap() function makes 3 copies. That leads to a lot of excessive string creation and destruction, which is slow.
  • However, doing copies isn’t necessary here. All we’re really trying to do is swap the values of a and b , which can be accomplished just as well using 3 moves instead! So if we switch from copy semantics to move semantics, we can make our code more performant.
<class T> void swap(T& a, T& b) { T tmp { std::move(a) }; // invokes move constructor a = std::move(b); // invokes move assignment b = std::move(tmp); // invokes move assignment }
  • std::move can also be useful when sorting an array of elements. Many sorting algorithms (such as selection sort and bubble sort) work by swapping pairs of elements. Here we can use move semantics, which is more efficient.

Reflection for C++26

Document #: P2996R5
Date: 2024-08-14
Project: Programming Language C++
Audience: CWG, LEWG
Reply-to: Wyatt Childers
< >
Peter Dimov
< >
Dan Katz
< >
Barry Revzin
< >
Andrew Sutton
< >
Faisal Vali
< >
Daveed Vandevoorde
< >

1 Revision History

2.1 notable additions to p1240, 2.2 why a single opaque reflection type, 2.3 implementation status, 3.1 back-and-forth, 3.2 selecting members, 3.3 list of types to list of sizes, 3.4 implementing make_integer_sequence, 3.5 getting class layout, 3.6 enum to string, 3.7 parsing command-line options, 3.8 a simple tuple type, 3.9 a simple variant type, 3.10 struct to struct of arrays, 3.11 parsing command-line options ii, 3.12 a universal formatter, 3.13 implementing member-wise hash_append, 3.14 converting a struct to a tuple, 3.15 implementing tuple_cat, 3.16 named tuple, 3.17 compile-time ticket counter, 3.18 emulating typeful reflection, 4.1.1 syntax discussion, 4.2.1 addressed splicing, 4.2.2.1 splicing reflections of constructors, 4.2.2.2 splicing namespaces in namespace definitions, 4.2.2.3 splicing namespaces in using-directives and using-enum-declarators, 4.2.2.4 splicing concepts in declarations of template parameters, 4.2.2.5 splicing class members as designators in designated-initializer-lists, 4.2.3 range splicers, 4.2.4 syntax discussion, 4.3.1 comparing reflections, 4.3.2 linkage of reflections and templates specialized by reflections, 4.3.3 the associated std :: meta namespace, 4.4.1 constant evaluation order, 4.4.2 error-handling in reflection, 4.4.3 range-based metafunctions, 4.4.4 handling aliases, 4.4.5 reflecting source text, 4.4.6 reflecting names, 4.4.7 freestanding implementations, 4.4.8 synopsis, 4.4.9 identifier_of , display_string_of , source_location_of, 4.4.10 type_of , parent_of , dealias, 4.4.11 object_of , value_of, 4.4.12 template_of , template_arguments_of, 4.4.13 members_of , static_data_members_of , nonstatic_data_members_of , bases_of , enumerators_of , subobjects_of, 4.4.14 member access reflection, 4.4.15 substitute, 4.4.16 reflect_invoke, 4.4.17 reflect_value , reflect_object , reflect_function, 4.4.18 extract < t >, 4.4.19 data_member_spec , define_class, 4.4.20 define_static_string, 4.4.21 data layout reflection, 4.4.22 other type traits, 4.5 odr concerns, 5.2 [lex.phases] phases of translation, 5.4 [lex.pptoken] preprocessing tokens, 5.12 [lex.operators] operators and punctuators, 6.3 [basic.def.odr] one-definition rule, 6.5.4 [basic.lookup.argdep] argument-dependent name lookup, 6.5.5.1 [basic.lookup.qual.general] general, 6.6 [basic.link] program and linkage, 6.8.1 [basic.types.general] general, 6.8.2 [basic.fundamental] fundamental types, 7.5 [expr.prim] primary expressions, 7.5.4.0* [expr.prim.id.splice] splice specifiers, 7.5.4.1 [expr.prim.id.general] general, 7.5.4.3 [expr.prim.id.qual] qualified names, 7.5.8* [expr.prim.splice] expression splicing, 7.6.1.1 [expr.post.general] general, 7.6.1.5 [expr.ref] class member access, 7.6.2.1 [expr.unary.general] general, 7.6.2.10* [expr.reflect] the reflection operator, 7.6.10 [expr.eq] equality operators, 7.7 [expr.const] constant expressions, 9.2.9.3 [dcl.type.simple] simple type specifiers, 9.2.9* [dcl.type.splice] type splicing, 9.4.1 [dcl.init.general] initializers (general), 9.3.4.6 [dcl.fct] functions, 9.5.3 [dcl.fct.def.delete] deleted definitions, 9.7.2 [enum.udecl] the using enum declaration, 9.8.3 [namespace.alias] namespace alias, 9.8.4 [namespace.udir] using namespace directive, 9.12.1 [dcl.attr.grammar] attribute syntax and semantics, 12.5 [over.built] built-in operators, 13.2 [temp.param] template parameters, 13.3 [temp.names] names of template specializations, 13.4.1 [temp.arg.general] general, 13.4.2 [temp.arg.type] template type arguments, 13.4.3 [temp.arg.nontype] template non-type arguments, 13.4.4 [temp.arg.template] template template arguments, 13.6 [temp.type] type equivalence, 13.7.9 [temp.concept] concept definitions, 13.8.3.3 [temp.dep.expr] type-dependent expressions, 13.8.3.4 [temp.dep.constexpr] value-dependent expressions, 5.2.1 16.3.2.4 [structure.specifications] detailed specifications, 5.2.2 16.4.5.2.1 [namespace.std] namespace std, 5.2.3 21.3.3 [meta.type.synop] header < type_traits > synopsis, 5.2.4 21.3.5.2 [meta.unary.cat] primary type categories, [meta.synop] header < meta > synopsis, 5.2.5 [meta.reflection.operators] operator representations, [meta.reflection.names] reflection names and locations, [meta.reflection.queries] reflection queries, [meta.reflection.member.queries], reflection member queries, 5.2.6 [meta.reflection.member.access], reflection member access queries, [meta.reflection.layout] reflection layout queries, [meta.reflection.extract] value extraction, [meta.reflection.substitute] reflection substitution, [meta.reflection.result] expression result reflection, [meta.reflection.define_class] reflection class definition generation, [meta.reflection.static_string] static string generation, [meta.reflection.unary.cat] primary type categories, [meta.reflection.unary.comp] composite type categories, [meta.reflection.unary.prop] type properties, [meta.reflection.unary.prop.query] type property queries, [meta.reflection.rel], type relations, [meta.reflection.trans.cv], const-volatile modifications, [meta.reflection.trans.ref], reference modifications, [meta.reflection.trans.sign], sign modifications, [meta.reflection.trans.arr], array modifications, [meta.reflection.trans.ptr], pointer modifications, [meta.reflection.trans.other], other transformations, 5.3 feature-test macro, 6 references.

Since [ P2996R4 ] :

  • removed filters from query functions
  • cleaned up accessibility interface, removed access_pair type, and redid API to be based on an access_context
  • reduced specification of is_noexcept
  • changed span < info const > to initializer_list < info >
  • removed test_trait
  • removed ( u8 ) name_of and ( u8 ) qualified_name_of ; added ( u8 ) identifier_of , operator_of , define_static_string .
  • renamed display_name_of to display_string_of
  • adding a number of missing predicates: is_enumerator , is_copy_constructor , is_move_constructor , is_assignment , is_move_assignment , is_copy_assignment , is_default_constructor , has_default_member_initializer , is_lvalue_reference_qualified , is_rvalue_reference_qualified , is_literal_operator ( _template ) , is_conversion_function ( _template ) , is_operator ( _template ) , is_data_member_spec , has_ ( thread | automatic ) _storage_duration
  • changed offset API to be one function that returns a type with named members
  • Tightened constraints on calls to data_member_spec , and defined comparison among reflections returned by it.
  • changed is_alias to is_ ( type | namespace ) _alias
  • changed is_incomplete_type to is_complete_type
  • Many wording updates in response to feedback from CWG.

Since [ P2996R3 ] :

  • changes to name functions to improve Unicode-friendliness; added u8name_of , u8qualified_name_of , u8display_name_of .
  • the return of reflect_value : separated reflect_result into three functions: reflect_value , reflect_object , reflect_function
  • more strongly specified comparison and linkage rules for reflections of aliases
  • changed is_noexcept to apply to a wider class of entities
  • reworked the API for reflecting on accessible class members
  • renamed test_type and test_types to test_trait
  • added missing has_module_linkage metafunction
  • clarified difference between a reflection of a variable and its object; added object_of metafunction
  • more wording

Since [ P2996R2 ] :

  • many wording changes, additions, and improvements
  • elaborated on equivalence among reflections and linkage of templated entities specialized by reflections
  • added accessible_members_of variants to restore a TS-era agreement
  • renamed function previously called value_of to extract , and expanded it to operate on functions
  • clarified support for reflections of values and objects rather than constant expressions
  • added Godbolt links to Clang/P2996 implementation
  • added can_substitute , is_value , is_object , and (new) value_of
  • added explanation of a naming issue with the type traits
  • added an alternative named tuple implementation
  • made default/value/zero-initializing a meta :: info yield a null reflection
  • added addressed splicing, which is implemented but was omitted from the paper
  • added another overload to reflect_invoke to support template arguments
  • renamed all the type traits to start with type_ to avoid name clashes. added more generalized is_const , is_final , and is_volatile
  • added is_noexcept and fixed is_explicit to only apply to member functions, not member function templates
  • added section on handling text
  • added a section discussing ODR concerns

Since [ P2996R1 ] , several changes to the overall library API:

  • added qualified_name_of (to partner with name_of )
  • removed is_static for being ambiguous, added has_internal_linkage (and has_linkage and has_external_linkage ) and is_static_member instead
  • added is_class_member , is_namespace_member , and is_concept
  • added reflect_invoke
  • added all the type traits

Other paper changes:

  • some updates to examples, including a new examples which add a named tuple and emulate typeful reflection .
  • more discussion of syntax, constant evaluation order, aliases, and freestanding.
  • adding lots of wording

Since [ P2996R0 ] :

  • added links to Compiler Explorer demonstrating just about all of the examples
  • respecified synth_struct to define_class
  • respecified a few metafunctions to be functions instead of function templates
  • introduced section on error handling mechanism and our preference for exceptions (removing invalid reflections)
  • added ticket counter and variant examples
  • collapsed entity_ref and pointer_to_member into value_of

2 Introduction

This is a proposal for a reduced initial set of features to support static reflection in C++. Specifically, we are mostly proposing a subset of features suggested in [ P1240R2 ] :

  • the representation of program elements via constant-expressions producing reflection values — reflections for short — of an opaque type std :: meta :: info ,
  • a reflection operator (prefix ^ ) that produces a reflection value for its operand construct,
  • a number of consteval metafunctions to work with reflections (including deriving other reflections), and
  • constructs called splicers to produce grammatical elements from reflections (e.g., [: refl :] ).

(Note that this aims at something a little broader than pure “reflection”. We not only want to observe the structure of the program: We also want to ease generating code that depends on those observations. That combination is sometimes referred to as “reflective metaprogramming”, but within WG21 discussion the term “reflection” has often been used informally to refer to the same general idea.)

This proposal is not intended to be the end-game as far as reflection and compile-time metaprogramming are concerned. Instead, we expect it will be a useful core around which more powerful features will be added incrementally over time. In particular, we believe that most or all the remaining features explored in P1240R2 and that code injection (along the lines described in [ P2237R0 ] ) are desirable directions to pursue.

Our choice to start with something smaller is primarily motivated by the belief that that improves the chances of these facilities making it into the language sooner rather than later.

While we tried to select a useful subset of the P1240 features, we also made a few additions and changes. Most of those changes are minor. For example, we added a std :: meta :: test_trait interface that makes it convenient to use existing standard type predicates (such as is_class_v ) in reflection computations.

One addition does stand out, however: We have added metafunctions that permit the synthesis of simple struct and union types. While it is not nearly as powerful as generalized code injection (see [ P2237R0 ] ), it can be remarkably effective in practice.

Perhaps the most common suggestion made regarding the framework outlined in P1240 is to switch from the single std :: meta :: info type to a family of types covering various language elements (e.g., std :: meta :: variable , std :: meta :: type , etc.).

We believe that doing so would be a mistake with very serious consequences for the future of C++.

Specifically, it would codify the language design into the type system. We know from experience that it has been quasi-impossible to change the semantics of standard types once they were standardized, and there is no reason to think that such evolution would become easier in the future. Suppose for example that we had standardized a reflection type std :: meta :: variable in C++03 to represent what the standard called “variables” at the time. In C++11, the term “variable” was extended to include “references”. Such an change would have been difficult to do given that C++ by then likely would have had plenty of code that depended on a type arrangement around the more restricted definition of “variable”. That scenario is clearly backward-looking, but there is no reason to believe that similar changes might not be wanted in the future and we strongly believe that it behooves us to avoid adding undue constraints on the evolution of the language.

Other advantages of a single opaque type include:

  • it makes no assumptions about the representation used within the implementation (e.g., it doesn’t advantage one compiler over another),
  • it is trivially extensible (no types need to be added to represent additional language elements and meta-elements as the language evolves), and
  • it allows convenient collections of heterogeneous constructs without having to surface reference semantics (e.g., a std :: vector < std :: meta :: info > can easily represent a mixed template argument list — containing types and nontypes — without fear of slicing values).

Lock3 implemented the equivalent of much that is proposed here in a fork of Clang (specifically, it worked with the P1240 proposal, but also included several other capabilities including a first-class injection mechanism).

EDG has an ongoing implementation of this proposal that is currently available on Compiler Explorer (thank you, Matt Godbolt).

Additionally, Bloomberg has open sourced a fork of Clang which provides a second implementation of this proposal, also available on Compiler Explorer (again thank you, Matt Godbolt), which can be found here: https://github.com/bloomberg/clang-p2996 .

Neither implementation is complete, but all significant features proposed by this paper have been implemented by at least one implementation (including namespace and template splicers). Both implementations have their “quirks” and continue to evolve alongside this paper.

Nearly all of the examples below have links to Compiler Explorer demonstrating them in both EDG and Clang.

The implementations notably lack some of the other proposed language features that dovetail well with reflection; most notably, expansion statements are absent. A workaround that will be used in the linked implementations of examples is the following facility:

namespace __impl { template < auto ... vals > struct replicator_type { template < typename F > constexpr void operator >>( F body ) const { ( body . template operator ()< vals >() , ...) ; } } ; template < auto ... vals > replicator_type < vals ...> replicator = {} ; } template < typename R > consteval auto expand ( R range ) { std :: vector < std :: meta :: info > args; for ( auto r : range ) { args . push_back ( reflect_value ( r )) ; } return substitute (^ __impl :: replicator, args ) ; }
workaround
template <typename E> requires std::is_enum_v<E> constexpr std::string enum_to_string(E value) { template for (constexpr auto e : std::meta::enumerators_of(^E)) { if (value == [:e:]) { return std::string(std::meta::identifier_of(e)); } } return "<unnamed>"; } template<typename E> requires std::is_enum_v<E> constexpr std::string enum_to_string(E value) { std::string result = "<unnamed>"; [:expand(std::meta::enumerators_of(^E)):] >> [&]<auto e>{ if (value == [:e:]) { result = std::meta::identifier_of(e); } }; return result; }

We start with a number of examples that show off what is possible with the proposed set of features. It is expected that these are mostly self-explanatory. Read ahead to the next sections for a more systematic description of each element of this proposal.

A number of our examples here show a few other language features that we hope to progress at the same time. This facility does not strictly rely on these features, and it is possible to do without them - but it would greatly help the usability experience if those could be adopted as well:

  • expansion statements [ P1306R2 ]
  • non-transient constexpr allocation [ P0784R7 ] [ P1974R0 ] [ P2670R1 ]

Our first example is not meant to be compelling but to show how to go back and forth between the reflection domain and the grammatical domain:

constexpr auto r = ^ int ; typename [: r :] x = 42 ; // Same as: int x = 42; typename [:^ char :] c = '*' ; // Same as: char c = '*';

The typename prefix can be omitted in the same contexts as with dependent qualified names (i.e., in what the standard calls type-only contexts ). For example:

using MyType = [: sizeof ( int )< sizeof ( long )? ^ long : ^ int :] ; // Implicit "typename" prefix.

On Compiler Explorer: EDG , Clang .

Our second example enables selecting a member “by number” for a specific type:

struct S { unsigned i : 2 , j : 6 ; } ; consteval auto member_number ( int n ) { if ( n == 0 ) return ^ S :: i; else if ( n == 1 ) return ^ S :: j; } int main () { S s { 0 , 0 } ; s .[: member_number ( 1 ):] = 42 ; // Same as: s.j = 42; s .[: member_number ( 5 ):] = 0 ; // Error (member_number(5) is not a constant). }

This example also illustrates that bit fields are not beyond the reach of this proposal.

Note that a “member access splice” like s .[: member_number ( 1 ):] is a more direct member access mechanism than the traditional syntax. It doesn’t involve member name lookup, access checking, or — if the spliced reflection value represents a member function — overload resolution.

This proposal includes a number of consteval “metafunctions” that enable the introspection of various language constructs. Among those metafunctions is std :: meta :: nonstatic_data_members_of which returns a vector of reflection values that describe the non-static members of a given type. We could thus rewrite the above example as:

struct S { unsigned i : 2 , j : 6 ; } ; consteval auto member_number ( int n ) { return std :: meta :: nonstatic_data_members_of (^ S )[ n ] ; } int main () { S s { 0 , 0 } ; s .[: member_number ( 1 ):] = 42 ; // Same as: s.j = 42; s .[: member_number ( 5 ):] = 0 ; // Error (member_number(5) is not a constant). }

This proposal specifies that namespace std :: meta is associated with the reflection type ( std :: meta :: info ); the std :: meta :: qualification can therefore be omitted in the example above.

Another frequently-useful metafunction is std :: meta :: identifier_of , which returns a std :: string_view describing the identifier with which an entity represented by a given reflection value was declared. With such a facility, we could conceivably access non-static data members “by string”:

struct S { unsigned i : 2 , j : 6 ; } ; consteval auto member_named ( std :: string_view name ) { for ( std :: meta :: info field : nonstatic_data_members_of (^ S )) { if ( has_identifier ( field ) && identifier_of ( field ) == name ) return field; } } int main () { S s { 0 , 0 } ; s .[: member_named ( "j" ):] = 42 ; // Same as: s.j = 42; s .[: member_named ( "x" ):] = 0 ; // Error (member_named("x") is not a constant). }

Here, sizes will be a std :: array < std :: size_t , 3 > initialized with { sizeof ( int ) , sizeof ( float ) , sizeof ( double )} :

constexpr std :: array types = {^ int , ^ float , ^ double } ; constexpr std :: array sizes = []{ std :: array < std :: size_t , types . size ()> r; std :: views :: transform ( types, r . begin () , std :: meta :: size_of ) ; return r; }() ;

Compare this to the following type-based approach, which produces the same array sizes :

template < class ...> struct list {} ; using types = list < int , float , double > ; constexpr auto sizes = []< template < class ...> class L, class ... T >( L < T ...>) { return std :: array < std :: size_t , sizeof ...( T )>{{ sizeof ( T )... }} ; }( types {}) ;

We can provide a better implementation of make_integer_sequence than a hand-rolled approach using regular template metaprogramming (although standard libraries today rely on an intrinsic for this):

#include <utility> #include <vector> template < typename T > consteval std :: meta :: info make_integer_seq_refl ( T N ) { std :: vector args {^ T } ; for ( T k = 0 ; k < N; ++ k ) { args . push_back ( std :: meta :: reflect_value ( k )) ; } return substitute (^ std :: integer_sequence, args ) ; } template < typename T, T N > using make_integer_sequence = [: make_integer_seq_refl < T >( N ):] ;

Note that the memoization implicit in the template substitution process still applies. So having multiple uses of, e.g., make_integer_sequence < int , 20 > will only involve one evaluation of make_integer_seq_refl < int >( 20 ) .

struct member_descriptor { std :: size_t offset; std :: size_t size; } ; // returns std::array<member_descriptor, N> template < typename S > consteval auto get_layout () { constexpr auto members = nonstatic_data_members_of (^ S ) ; std :: array < member_descriptor, members . size ()> layout; for ( int i = 0 ; i < members . size () ; ++ i ) { layout [ i ] = {. offset = offset_of ( members [ i ]). bytes, . size = size_of ( members [ i ])} ; } return layout; } struct X { char a; int b; double c; } ; /*constexpr*/ auto Xd = get_layout < X >() ; /* where Xd would be std::array<member_descriptor, 3>{{ { 0, 1 }, { 4, 4 }, { 8, 8 } }} */

One of the most commonly requested facilities is to convert an enum value to a string (this example relies on expansion statements):

template < typename E > requires std :: is_enum_v < E > constexpr std :: string enum_to_string ( E value ) { template for ( constexpr auto e : std :: meta :: enumerators_of (^ E )) { if ( value == [: e :]) { return std :: string ( std :: meta :: identifier_of ( e )) ; } } return "<unnamed>" ; } enum Color { red, green, blue } ; static_assert ( enum_to_string ( Color :: red ) == "red" ) ; static_assert ( enum_to_string ( Color ( 42 )) == "<unnamed>" ) ;

We can also do the reverse in pretty much the same way:

template < typename E > requires std :: is_enum_v < E > constexpr std :: optional < E > string_to_enum ( std :: string_view name ) { template for ( constexpr auto e : std :: meta :: enumerators_of (^ E )) { if ( name == std :: meta :: identifier_of ( e )) { return [: e :] ; } } return std :: nullopt; }

But we don’t have to use expansion statements - we can also use algorithms. For instance, enum_to_string can also be implemented this way (this example relies on non-transient constexpr allocation), which also demonstrates choosing a different algorithm based on the number of enumerators:

template < typename E > requires std :: is_enum_v < E > constexpr std :: string enum_to_string ( E value ) { constexpr auto get_pairs = []{ return std :: meta :: enumerators_of (^ E ) | std :: views :: transform ([]( std :: meta :: info e ){ return std :: pair < E, std :: string >( std :: meta :: extract < E >( e ) , std :: meta :: identifier_of ( e )) ; }) } ; constexpr auto get_name = []( E value ) -> std :: optional < std :: string > { if constexpr ( enumerators_of (^ E ). size () <= 7 ) { // if there aren't many enumerators, use a vector with find_if() constexpr auto enumerators = get_pairs () | std :: ranges :: to < std :: vector >() ; auto it = std :: ranges :: find_if ( enumerators, [ value ]( auto const & pr ){ return pr . first == value; } ; if ( it == enumerators . end ()) { return std :: nullopt; } else { return it -> second; } } else { // if there are lots of enumerators, use a map with find() constexpr auto enumerators = get_pairs () | std :: ranges :: to < std :: map >() ; auto it = enumerators . find ( value ) ; if ( it == enumerators . end ()) { return std :: nullopt; } else { return it -> second; } } } ; return get_name ( value ). value_or ( "<unnamed>" ) ; }

Note that this last version has lower complexity: While the versions using an expansion statement use an expected O(N) number of comparisons to find the matching entry, a std :: map achieves the same with O(log(N)) complexity (where N is the number of enumerator constants).

Many many variations of these functions are possible and beneficial depending on the needs of the client code. For example:

  • the “<unnamed>” case could instead output a valid cast expression like “E(5)”
  • a more sophisticated lookup algorithm could be selected at compile time depending on the length of enumerators_of (^ E )
  • a compact two-way persistent data structure could be generated to support both enum_to_string and string_to_enum with a minimal footprint

Our next example shows how a command-line option parser could work by automatically inferring flags based on member names. A real command-line parser would of course be more complex, this is just the beginning.

template < typename Opts > auto parse_options ( std :: span < std :: string_view const > args ) -> Opts { Opts opts; template for ( constexpr auto dm : nonstatic_data_members_of (^ Opts )) { auto it = std :: ranges :: find_if ( args, []( std :: string_view arg ){ return arg . starts_with ( "--" ) && arg . substr ( 2 ) == identifier_of ( dm ) ; }) ; if ( it == args . end ()) { // no option provided, use default continue ; } else if ( it + 1 == args . end ()) { std :: print ( stderr, "Option {} is missing a value \n " , * it ) ; std :: exit ( EXIT_FAILURE ) ; } using T = typename [: type_of ( dm ):] ; auto iss = std :: ispanstream ( it [ 1 ]) ; if ( iss >> opts .[: dm :] ; ! iss ) { std :: print ( stderr, "Failed to parse option {} into a {} \n " , * it, display_string_of (^ T )) ; std :: exit ( EXIT_FAILURE ) ; } } return opts; } struct MyOpts { std :: string file_name = "input.txt" ; // Option "--file_name <string>" int count = 1 ; // Option "--count <int>" } ; int main ( int argc, char * argv []) { MyOpts opts = parse_options < MyOpts >( std :: vector < std :: string_view >( argv + 1 , argv + argc )) ; // ... }

This example is based on a presentation by Matúš Chochlík.

#include <meta> template < typename ... Ts > struct Tuple { struct storage; static_assert ( is_type ( define_class (^ storage, { data_member_spec (^ Ts )...}))) ; storage data; Tuple (): data {} {} Tuple ( Ts const & ... vs ): data { vs ... } {} } ; template < typename ... Ts > struct std :: tuple_size < Tuple < Ts ...>>: public integral_constant < size_t , sizeof ...( Ts )> {} ; template < std :: size_t I, typename ... Ts > struct std :: tuple_element < I, Tuple < Ts ...>> { static constexpr std :: array types = {^ Ts ...} ; using type = [: types [ I ] :] ; } ; consteval std :: meta :: info get_nth_field ( std :: meta :: info r, std :: size_t n ) { return nonstatic_data_members_of ( r )[ n ] ; } template < std :: size_t I, typename ... Ts > constexpr auto get ( Tuple < Ts ...> & t ) noexcept -> std :: tuple_element_t < I, Tuple < Ts ...>>& { return t . data .[: get_nth_field (^ decltype ( t . data ) , I ):] ; } // Similarly for other value categories...

This example uses a “magic” std :: meta :: define_class template along with member reflection through the nonstatic_data_members_of metafunction to implement a std :: tuple -like type without the usual complex and costly template metaprogramming tricks that that involves when these facilities are not available. define_class takes a reflection for an incomplete class or union plus a vector of non-static data member descriptions, and completes the give class or union type to have the described members.

Similarly to how we can implement a tuple using define_class to create on the fly a type with one member for each Ts ... , we can implement a variant that simply defines a union instead of a struct . One difference here is how the destructor of a union is currently defined:

union U1 { int i; char c; } ; union U2 { int i; std :: string s; } ;

U1 has a trivial destructor, but U2 ’s destructor is defined as deleted (because std :: string has a non-trivial destructor). This is a problem because we need to define this thing… somehow. However, for the purposes of define_class , there really is only one reasonable option to choose here:

template < class ... Ts > union U { // all of our members Ts ... members; // a defaulted destructor if all of the types are trivially destructible constexpr ~ U () requires ( std :: is_trivially_destructible_v < Ts > && ...) = default ; // ... otherwise a destructor that does nothing constexpr ~ U () { } } ;

If we make define_class for a union have this behavior, then we can implement a variant in a much more straightforward way than in current implementations. This is not a complete implementation of std :: variant (and cheats using libstdc++ internals, and also uses Boost.Mp11’s mp_with_index ) but should demonstrate the idea:

template < typename ... Ts > class Variant { union Storage; struct Empty { } ; static_assert ( is_type ( define_class (^ Storage, { data_member_spec (^ Empty, {. name = "empty" }) , data_member_spec (^ Ts )... }))) ; static consteval std :: meta :: info get_nth_field ( std :: size_t n ) { return nonstatic_data_members_of (^ Storage )[ n + 1 ] ; } Storage storage_; int index_ = - 1 ; // cheat: use libstdc++'s implementation template < typename T > static constexpr size_t accepted_index = std :: __detail :: __variant :: __accepted_index < T, std :: variant < Ts ...>> ; template < class F > constexpr auto with_index ( F && f ) const -> decltype ( auto ) { return mp_with_index < sizeof ...( Ts )>( index_, ( F &&) f ) ; } public : constexpr Variant () requires std :: is_default_constructible_v < Ts ...[ 0 ]> // should this work: storage_{. [: get_nth_field(0) :]{} } : storage_ {. empty ={}} , index_ ( 0 ) { std :: construct_at (& storage_ .[: get_nth_field ( 0 ) :]) ; } constexpr ~ Variant () requires ( std :: is_trivially_destructible_v < Ts > and ...) = default ; constexpr ~ Variant () { if ( index_ != - 1 ) { with_index ([&]( auto I ){ std :: destroy_at (& storage_ .[: get_nth_field ( I ) :]) ; }) ; } } template < typename T, size_t I = accepted_index < T &&>> requires (! std :: is_base_of_v < Variant, std :: decay_t < T >>) constexpr Variant ( T && t ) : storage_ {. empty ={}} , index_ (- 1 ) { std :: construct_at (& storage_ .[: get_nth_field ( I ) :] , ( T &&) t ) ; index_ = ( int ) I; } // you can't actually express this constraint nicely until P2963 constexpr Variant ( Variant const &) requires ( std :: is_trivially_copyable_v < Ts > and ...) = default ; constexpr Variant ( Variant const & rhs ) requires (( std :: is_copy_constructible_v < Ts > and ...) and not ( std :: is_trivially_copyable_v < Ts > and ...)) : storage_ {. empty ={}} , index_ (- 1 ) { rhs . with_index ([&]( auto I ){ constexpr auto field = get_nth_field ( I ) ; std :: construct_at (& storage_ .[: field :] , rhs . storage_ .[: field :]) ; index_ = I; }) ; } constexpr auto index () const -> int { return index_; } template < class F > constexpr auto visit ( F && f ) const -> decltype ( auto ) { if ( index_ == - 1 ) { throw std :: bad_variant_access () ; } return mp_with_index < sizeof ...( Ts )>( index_, [&]( auto I ) -> decltype ( auto ) { return std :: invoke (( F &&) f, storage_ .[: get_nth_field ( I ) :]) ; }) ; } } ;

Effectively, Variant < T, U > synthesizes a union type Storage which looks like this:

union Storage { Empty empty; T unnamed 0 ; U unnamed 1 ; ~ Storage () requires std :: is_trivially_destructible_v < T > && std :: is_trivially_destructible_v < U > = default ; ~ Storage () { } }

The question here is whether we should be should be able to directly initialize members of a defined union using a splicer, as in:

: storage {.[: get_nth_field ( 0 ) :]={}}

Arguably, the answer should be yes - this would be consistent with how other accesses work. This is instead proposed in [ P3293R0 ] .

#include <meta> #include <array> template < typename T, std :: size_t N > struct struct_of_arrays_impl; consteval auto make_struct_of_arrays ( std :: meta :: info type, std :: meta :: info N ) -> std :: meta :: info { std :: vector < std :: meta :: info > old_members = nonstatic_data_members_of ( type ) ; std :: vector < std :: meta :: info > new_members = {} ; for ( std :: meta :: info member : old_members ) { auto type_array = substitute (^ std :: array, { type_of ( member ) , N }) ; auto mem_descr = data_member_spec ( type_array, {. name = identifier_of ( member )}) ; new_members . push_back ( mem_descr ) ; } return std :: meta :: define_class ( substitute (^ struct_of_arrays_impl, { type, N }) , new_members ) ; } template < typename T, size_t N > using struct_of_arrays = [: make_struct_of_arrays (^ T, ^ N ) :] ;
struct point { float x; float y; float z; } ; using points = struct_of_arrays < point, 30 > ; // equivalent to: // struct points { // std::array<float, 30> x; // std::array<float, 30> y; // std::array<float, 30> z; // };

Again, the combination of nonstatic_data_members_of and define_class is put to good use.

Now that we’ve seen a couple examples of using std :: meta :: define_class to create a type, we can create a more sophisticated command-line parser example.

This is the opening example for clap (Rust’s C ommand L ine A rgument P arser):

struct Args : Clap { Option < std :: string, {. use_short = true , . use_long = true }> name; Option < int , {. use_short = true , . use_long = true }> count = 1 ; } ; int main ( int argc, char ** argv ) { auto opts = Args {}. parse ( argc, argv ) ; for ( int i = 0 ; i < opts . count; ++ i ) { // opts.count has type int std :: print ( "Hello {}!" , opts . name ) ; // opts.name has type std::string } }

Which we can implement like this:

struct Flags { bool use_short; bool use_long; } ; template < typename T, Flags flags > struct Option { std :: optional < T > initializer = {} ; // some suitable constructors and accessors for flags } ; // convert a type (all of whose non-static data members are specializations of Option) // to a type that is just the appropriate members. // For example, if type is a reflection of the Args presented above, then this // function would evaluate to a reflection of the type // struct { // std::string name; // int count; // } consteval auto spec_to_opts ( std :: meta :: info opts, std :: meta :: info spec ) -> std :: meta :: info { std :: vector < std :: meta :: info > new_members; for ( std :: meta :: info member : nonstatic_data_members_of ( spec )) { auto type_new = template_arguments_of ( type_of ( member ))[ 0 ] ; new_members . push_back ( data_member_spec ( type_new, {. name = identifier_of ( member )})) ; } return define_class ( opts, new_members ) ; } struct Clap { template < typename Spec > auto parse ( this Spec const & spec, int argc, char ** argv ) { std :: vector < std :: string_view > cmdline ( argv + 1 , argv + argc ) // check if cmdline contains --help, etc. struct Opts; static_assert ( is_type ( spec_to_opts (^ Opts, ^ Spec ))) ; Opts opts; template for ( constexpr auto [ sm, om ] : std :: views :: zip ( nonstatic_data_members_of (^ Spec ) , nonstatic_data_members_of (^ Opts ))) { auto const & cur = spec .[: sm :] ; constexpr auto type = type_of ( om ) ; // find the argument associated with this option auto it = std :: ranges :: find_if ( cmdline, [&]( std :: string_view arg ){ return ( cur . use_short && arg . size () == 2 && arg [ 0 ] == '-' && arg [ 1 ] == identifier_of ( sm )[ 0 ]) || ( cur . use_long && arg . starts_with ( "--" ) && arg . substr ( 2 ) == identifier_of ( sm )) ; }) ; // no such argument if ( it == cmdline . end ()) { if constexpr ( has_template_arguments ( type ) and template_of ( type ) == ^ std :: optional ) { // the type is optional, so the argument is too continue ; } else if ( cur . initializer ) { // the type isn't optional, but an initializer is provided, use that opts .[: om :] = * cur . initializer; continue ; } else { std :: print ( stderr, "Missing required option {} \n " , display_string_of ( sm )) ; std :: exit ( EXIT_FAILURE ) ; } } else if ( it + 1 == cmdline . end ()) { std :: print ( stderr, "Option {} for {} is missing a value \n " , * it, display_string_of ( sm )) ; std :: exit ( EXIT_FAILURE ) ; } // found our argument, try to parse it auto iss = ispanstream ( it [ 1 ]) ; if ( iss >> opts .[: om :] ; ! iss ) { std :: print ( stderr, "Failed to parse {:?} into option {} of type {} \n " , it [ 1 ] , display_string_of ( sm ) , display_string_of ( type )) ; std :: exit ( EXIT_FAILURE ) ; } } return opts; } } ;

This example is taken from Boost.Describe:

struct universal_formatter { constexpr auto parse ( auto & ctx ) { return ctx . begin () ; } template < typename T > auto format ( T const & t, auto & ctx ) const { auto out = std :: format_to ( ctx . out () , "{}{{" , has_identifier (^ T ) ? identifier_of (^ T ) : "(unnamed-type)" ; ) ; auto delim = [ first = true ]() mutable { if (! first ) { * out ++ = ',' ; * out ++ = ' ' ; } first = false ; } ; template for ( constexpr auto base : bases_of (^ T )) { delim () ; out = std :: format_to ( out, "{}" , ( typename [: type_of ( base ) :] const &)( t )) ; } template for ( constexpr auto mem : nonstatic_data_members_of (^ T )) { delim () ; std :: string_view mem_label = has_identifier ( mem ) ? identifier_of ( mem ) : "(unnamed-member)" ; out = std :: format_to ( out, ".{}={}" , mem_label, t .[: mem :]) ; } * out ++ = '}' ; return out; } } ; struct B { int m0 = 0 ; } ; struct X { int m1 = 1 ; } ; struct Y { int m2 = 2 ; } ; class Z : public X, private Y { int m3 = 3 ; int m4 = 4 ; } ; template <> struct std :: formatter < B > : universal_formatter { } ; template <> struct std :: formatter < X > : universal_formatter { } ; template <> struct std :: formatter < Y > : universal_formatter { } ; template <> struct std :: formatter < Z > : universal_formatter { } ; int main () { std :: println ( "{}" , Z ()) ; // Z{X{B{.m0=0}, .m1 = 1}, Y{{.m0=0}, .m2 = 2}, .m3 = 3, .m4 = 4} }

On Compiler Explorer: Clang .

Note that currently, we do not have the ability to access a base class subobject using the t .[: base :] syntax - which means that the only way to get at the base is to use a cast:

  • static_cast <[: type_of ( base ) const & :]>( t ) , or
  • ( typename [: type_of ( base ) :] const &) t

Both have to explicitly specify the const -ness of the type in the cast. The static_cast additionally has to check access. The C-style cast is one many people find unsavory, though in this case it avoids checking access - but requires writing typename since this isn’t a type-only context.

Based on the [ N3980 ] API:

template < typename H, typename T > requires std :: is_standard_layout_v < T > void hash_append ( H & algo, T const & t ) { template for ( constexpr auto mem : nonstatic_data_members_of (^ T )) { hash_append ( algo, t .[: mem :]) ; } }

This approach requires allowing packs in structured bindings [ P1061R5 ] , but can also be written using std :: make_index_sequence :

template < typename T > constexpr auto struct_to_tuple ( T const & t ) { constexpr auto members = nonstatic_data_members_of (^ T ) ; constexpr auto indices = []{ std :: array < int , members . size ()> indices; std :: ranges :: iota ( indices, 0 ) ; return indices; }() ; constexpr auto [... Is ] = indices; return std :: make_tuple ( t .[: members [ Is ] :]...) ; }

An alternative approach is:

consteval auto type_struct_to_tuple ( info type ) -> info { return substitute (^ std :: tuple, nonstatic_data_members_of ( type ) | std :: views :: transform ( std :: meta :: type_of ) | std :: views :: transform ( std :: meta :: type_remove_cvref ) | std :: ranges :: to < std :: vector >()) ; } template < typename To, typename From, std :: meta :: info ... members > constexpr auto struct_to_tuple_helper ( From const & from ) -> To { return To ( from .[: members :]...) ; } template < typename From > consteval auto get_struct_to_tuple_helper () { using To = [: type_struct_to_tuple (^ From ): ] ; std :: vector args = {^ To, ^ From } ; for ( auto mem : nonstatic_data_members_of (^ From )) { args . push_back ( reflect_value ( mem )) ; } /* Alternatively, with Ranges: args.append_range( nonstatic_data_members_of(^From) | std::views::transform(std::meta::reflect_value) ); */ return extract < To (*)( From const &)>( substitute (^ struct_to_tuple_helper, args )) ; } template < typename From > constexpr auto struct_to_tuple ( From const & from ) { return get_struct_to_tuple_helper < From >()( from ) ; }

Here, type_struct_to_tuple takes a reflection of a type like struct { T t; U const & u; V v; } and returns a reflection of the type std :: tuple < T, U, V > . That gives us the return type. Then, struct_to_tuple_helper is a function template that does the actual conversion — which it can do by having all the reflections of the members as a non-type template parameter pack. This is a constexpr function and not a consteval function because in the general case the conversion is a run-time operation. However, determining the instance of struct_to_tuple_helper that is needed is a compile-time operation and has to be performed with a consteval function (because the function invokes nonstatic_data_members_of ), hence the separate function template get_struct_to_tuple_helper () .

Everything is put together by using substitute to create the instantiation of struct_to_tuple_helper that we need, and a compile-time reference to that instance is obtained with extract . Thus f is a function reference to the correct specialization of struct_to_tuple_helper , which we can simply invoke.

On Compiler Explorer (with a different implementation than either of the above): EDG , Clang .

Courtesy of Tomasz Kaminski, on compiler explorer :

template < std :: pair < std :: size_t , std :: size_t >... indices > struct Indexer { template < typename Tuples > // Can use tuple indexing instead of tuple of tuples auto operator ()( Tuples && tuples ) const { using ResultType = std :: tuple < std :: tuple_element_t < indices . second, std :: remove_cvref_t < std :: tuple_element_t < indices . first, std :: remove_cvref_t < Tuples >>> >... > ; return ResultType ( std :: get < indices . second >( std :: get < indices . first >( std :: forward < Tuples >( tuples )))...) ; } } ; template < class T > consteval auto subst_by_value ( std :: meta :: info tmpl, std :: vector < T > args ) -> std :: meta :: info { std :: vector < std :: meta :: info > a2; for ( T x : args ) { a2 . push_back ( std :: meta :: reflect_value ( x )) ; } return substitute ( tmpl, a2 ) ; } consteval auto make_indexer ( std :: vector < std :: size_t > sizes ) -> std :: meta :: info { std :: vector < std :: pair < int , int >> args; for ( std :: size_t tidx = 0 ; tidx < sizes . size () ; ++ tidx ) { for ( std :: size_t eidx = 0 ; eidx < sizes [ tidx ] ; ++ eidx ) { args . push_back ({ tidx, eidx }) ; } } return subst_by_value (^ Indexer, args ) ; } template < typename ... Tuples > auto my_tuple_cat ( Tuples &&... tuples ) { constexpr typename [: make_indexer ({ type_tuple_size ( type_remove_cvref (^ Tuples ))...}) :] indexer; return indexer ( std :: forward_as_tuple ( std :: forward < Tuples >( tuples )...)) ; }

The tricky thing with implementing a named tuple is actually strings as non-type template parameters. Because you cannot just pass "x" into a non-type template parameter of the form auto V , that leaves us with two ways of specifying the constituents:

  • Can introduce a pair type so that we can write make_named_tuple < pair < int , "x" > , pair < double , "y" >>() , or
  • Can just do reflections all the way down so that we can write

We do not currently support splicing string literals, and the pair approach follows the similar pattern already shown with define_class (given a suitable fixed_string type):

template < class T, fixed_string Name > struct pair { static constexpr auto name () -> std :: string_view { return Name . view () ; } using type = T; } ; template < class ... Tags > consteval auto make_named_tuple ( std :: meta :: info type, Tags ... tags ) { std :: vector < std :: meta :: info > nsdms; auto f = [&]< class Tag >( Tag tag ){ nsdms . push_back ( data_member_spec ( dealias (^ typename Tag :: type ) , {. name = Tag :: name ()})) ; } ; ( f ( tags ) , ...) ; return define_class ( type, nsdms ) ; } struct R; static_assert ( is_type ( make_named_tuple (^ R, pair < int , "x" >{} , pair < double , "y" >{}))) ; static_assert ( type_of ( nonstatic_data_members_of (^ R )[ 0 ]) == ^ int ) ; static_assert ( type_of ( nonstatic_data_members_of (^ R )[ 1 ]) == ^ double ) ; int main () { [[ maybe_unused ]] auto r = R {. x = 1 , . y = 2.0 } ; }

Alternatively, can side-step the question of non-type template parameters entirely by keeping everything in the value domain:

consteval auto make_named_tuple ( std :: meta :: info type, std :: initializer_list < std :: pair < std :: meta :: info, std :: string_view >> members ) { std :: vector < std :: meta :: data_member_spec > nsdms; for ( auto [ type, name ] : members ) { nsdms . push_back ( data_member_spec ( type, {. name = name })) ; } return define_class ( type, nsdms ) ; } struct R; static_assert ( is_type ( make_named_tuple (^ R, {{^ int , "x" } , {^ double , "y" }}))) ; static_assert ( type_of ( nonstatic_data_members_of (^ R )[ 0 ]) == ^ int ) ; static_assert ( type_of ( nonstatic_data_members_of (^ R )[ 1 ]) == ^ double ) ; int main () { [[ maybe_unused ]] auto r = R {. x = 1 , . y = 2.0 } ; }

On Compiler Explorer: EDG and Clang (the EDG and Clang implementations differ only in Clang having the updated data_member_spec API that returns an info ).

The features proposed here make it a little easier to update a ticket counter at compile time. This is not an ideal implementation (we’d prefer direct support for compile-time —– i.e., consteval — variables), but it shows how compile-time mutable state surfaces in new ways.

class TU_Ticket { template < int N > struct Helper; public : static consteval int next () { int k = 0 ; // Search for the next incomplete 'Helper<k>'. std :: meta :: info r; while ( is_complete_type ( r = substitute (^ Helper, { std :: meta :: reflect_value ( k ) }))) ++ k; // Define 'Helper<k>' and return its index. define_class ( r, {}) ; return k; } } ; constexpr int x = TU_Ticket :: next () ; static_assert ( x == 0 ) ; constexpr int y = TU_Ticket :: next () ; static_assert ( y == 1 ) ; constexpr int z = TU_Ticket :: next () ; static_assert ( z == 2 ) ;

Although we believe a single opaque std :: meta :: info type to be the best and most scalable foundation for reflection, we acknowledge the desire expressed by SG7 for future support for “typeful reflection”. The following demonstrates one possible means of assembling a typeful reflection library, in which different classes of reflections are represented by distinct types, on top of the facilities proposed here.

// Represents a 'std::meta::info' constrained by a predicate. template < std :: meta :: info Pred > requires ( extract < bool >( substitute (^ std :: predicate, { type_of ( Pred ) , ^ std :: meta :: info }))) struct metatype { std :: meta :: info value; // Construction is ill-formed unless predicate is satisfied. consteval metatype ( std :: meta :: info r ) : value ( r ) { if (![: Pred :]( r )) throw "Reflection is not a member of this metatype" ; } // Cast to 'std::meta::info' allows values of this type to be spliced. consteval operator std :: meta :: info () const { return value; } static consteval bool check ( std :: meta :: info r ) { return [: Pred :]( r ) ; } } ; // Type representing a "failure to match" any known metatypes. struct unmatched { consteval unmatched ( std :: meta :: info ) {} static consteval bool check ( std :: meta :: info ) { return true ; } } ; // Returns the given reflection "enriched" with a more descriptive type. template < typename ... Choices > consteval std :: meta :: info enrich ( std :: meta :: info r ) { // Because we control the type, we know that the constructor taking info is // the first constructor. The copy/move constructors are added at the }, so // will be the last ones in the list. std :: array ctors = { members_of (^ Choices, std :: meta :: is_constructor )[ 0 ]... , members_of (^ unmatched, std :: meta :: is_constructor )[ 0 ]} ; std :: array checks = {^ Choices :: check ... , ^ unmatched :: check } ; for ( auto [ check, ctor ] : std :: views :: zip ( checks, ctors )) if ( extract < bool >( reflect_invoke ( check, { reflect_value ( r )}))) return reflect_invoke ( ctor, { reflect_value ( r )}) ; std :: unreachable () ; }

We can leverage this machinery to select different function overloads based on the “type” of reflection provided as an argument.

using type_t = metatype <^ std :: meta :: is_type > ; using template_t = metatype <^ std :: meta :: is_template > ; // Example of a function overloaded for different "types" of reflections. void PrintKind ( type_t ) { std :: println ( "type" ) ; } void PrintKind ( template_t ) { std :: println ( "template" ) ; } void PrintKind ( unmatched ) { std :: println ( "unknown kind" ) ; } int main () { // Classifies any reflection as one of: Type, Function, or Unmatched. auto enrich = []( std :: meta :: info r ) { return :: enrich < type_t, template_t >( r ) ; } ; // Demonstration of using 'enrich' to select an overload. PrintKind ([: enrich (^ metatype ):]) ; // "template" PrintKind ([: enrich (^ type_t ):]) ; // "type" PrintKind ([: enrich ( std :: meta :: reflect_value ( 3 ):]) ; // "unknown kind" }

Note that the metatype class can be generalized to wrap values of any literal type, or to wrap multiple values of possibly different types. This has been used, for instance, to select compile-time overloads based on: whether two integers share the same parity, the presence or absence of a value in an optional , the type of the value held by a variant or an any , or the syntactic form of a compile-time string.

Achieving the same in C++23, with the same generality, would require spelling the argument(s) twice: first to obtain a “classification tag” to use as a template argument, and again to call the function, i.e.,

Printer :: PrintKind < classify (^ int )>(^ int ). // or worse... fn < classify ( Arg1, Arg2, Arg3 )>( Arg1, Arg2, Arg3 ).

4 Proposed Features

4.1 the reflection operator ( ^ ).

The reflection operator produces a reflection value from a grammatical construct (its operand):

unary-expression :       …        ^ ::        ^ namespace-name        ^ type-id        ^ id-expression

The expression ^:: evaluates to a reflection of the global namespace. When the operand is a namespace-name or type-id , the resulting value is a reflection of the designated namespace or type.

When the operand is an id-expression , the resulting value is a reflection of the designated entity found by lookup. This might be any of:

  • a variable, static data member, or structured binding
  • a function or member function
  • a non-static data member
  • a primary template or primary member template
  • an enumerator

For all other operands, the expression is ill-formed. In a SFINAE context, a failure to substitute the operand of a reflection operator construct causes that construct to not evaluate to constant.

Earlier revisions of this paper allowed for taking the reflection of any cast-expression that could be evaluated as a constant expression, as we believed that a constant expression could be internally “represented” by just capturing the value to which it evaluated. However, the possibility of side effects from constant evaluation (introduced by this very paper) renders this approach infeasible: even a constant expression would have to be evaluated every time it’s spliced. It was ultimately decided to defer all support for expression reflection, but we intend to introduce it through a future paper using the syntax ^( expr ) .

This paper does, however, support reflections of values and of objects (including subobjects). Such reflections arise naturally when iterating over template arguments.

Such reflections cannot generally be obtained using the ^ -operator, but the std :: meta :: reflect_value and std :: meta :: reflect_object functions make it easy to reflect particular values or objects. The std :: meta :: value_of metafunction can also be used to map a reflection of an object to a reflection of its value.

The original TS landed on reflexpr (...) as the syntax to reflect source constructs and [ P1240R0 ] adopted that syntax as well. As more examples were discussed, it became clear that that syntax was both (a) too “heavy” and (b) insufficiently distinct from a function call. SG7 eventually agreed upon the prefix ^ operator. The “upward arrow” interpretation of the caret matches the “lift” or “raise” verbs that are sometimes used to describe the reflection operation in other contexts.

The caret already has a meaning as a binary operator in C++ (“exclusive OR”), but that is clearly not conflicting with a prefix operator. In C++/CLI (a Microsoft C++ dialect) the caret is also used as a new kind of ptr-operator ( 9.3.1 [dcl.decl.general] ) to declare “handles” . That is also not conflicting with the use of the caret as a unary operator because C++/CLI uses the usual prefix * operator to dereference handles.

Apple also uses the caret in syntax “blocks” and unfortunately we believe that does conflict with our proposed use of the caret.

Since the syntax discussions in SG7 landed on the use of the caret, new basic source characters have become available: @ , ` , and $ . While we have since discussed some alternatives (e.g., @ for lifting, \ and / for “raising” and “lowering”), we have grown quite fond of the existing syntax.

4.2 Splicers ( [: … :] )

A reflection can be “spliced” into source code using one of several splicer forms:

  • [: r :] produces an expression evaluating to the entity represented by r in grammatical contexts that permit expressions. In type-only contexts ( 13.8.1 [temp.res.general] /4), [: r :] produces a type (and r must be the reflection of a type). In contexts that only permit a namespace name, [: r :] produces a namespace (and r must be the reflection of a namespace or alias thereof).
  • typename [: r :] produces a simple-type-specifier corresponding to the type represented by r .
  • template [: r :] produces a template-name corresponding to the template represented by r .
  • [: r :]:: produces a nested-name-specifier corresponding to the namespace, enumeration type, or class type represented by r .

The operand of a splicer is implicitly converted to a std :: meta :: info prvalue (i.e., if the operand expression has a class type that with a conversion function to convert to std :: meta :: info , splicing can still work).

Attempting to splice a reflection value that does not meet the requirement of the splice is ill-formed. For example:

typename [: ^:: :] x = 0 ; // Error.

In the same way that & C :: mem can produce a pointer, pointer to member data, pointer to function, or pointer to member function depending on what mem refers to, &[: r :] can likewise produce the same set of pointers if r is a reflection of a suitable entity:

  • If r is a reflection of a static data member or a variable, &[: r :] is a pointer.
  • Otherwise if r is a reflection of a non-static data member, &[: r :] is a pointer to data member.
  • Otherwise, if r is a reflection of a static member function, a function, or a non-static member function with an explicit object parameter, &[: r :] is a pointer to function
  • Otherwise, if r is a reflection of a non-static member function with an implicit object parameter, &[: r :] is a pointer to member function.
  • Otherwise, if r is a reflection of a function template or member function template, &[: r :] is the address of that overload set - which would then require external context to resolve as usual.

For most members, this doesn’t even require any additional wording since that’s just what you get when you take the address of the splice based on the current rules we have today.

Now, there are a couple interesting cases to point out when &[: r :] isn’t just the same as & X :: f .

When r is a reflection of a function or function template that is part of an overload set, overload resolution will not consider the whole overload set, just the specific function or function template that r represents:

struct C { template < class T > void f ( T ) ; // #1 void f ( int ) ; // #2 } ; void ( C ::* p1 )( int ) = & C :: f; // error: ambiguous constexpr auto f1 = members_of (^ C, /* function templates named f */ )[ 0 ] ; constexpr auto f2 = members_of (^ C, /* functions named f */ )[ 0 ] ; void ( C ::* p2 )( int ) = &[: f1 :] ; // ok, refers to C::f<int> (#1) void ( C ::* p3 )( int ) = &[: f2 :] ; // ok, refers to C::f (#2)

Another interesting question is what does this mean when r is the reflection of a constructor or destructor? Consider the type:

struct X { X ( int , int ) ; } ;

And let rc be a reflection of the constructor and rd be a reflection of the destructor. The sensible syntax and semantics for how you would use rc and rd should be as follows:

auto x = [: rc :]( 1 , 2 ) ; // gives you an X x .[: rd :]() ; // destroys it

Or, with pointers:

auto pc = &[: rc :] ; auto pd = &[: rd :] ; auto x = (* pc )( 1 , 2 ) ; // gives you an X ( x .* pd )() ; // destroys it

That is, splicing a constructor behaves like a free function that produces an object of that type, so &[: rc :] has type X (*)( int , int ) . On the other hand, splicing a destructor behaves like a regular member function, so &[: rd :] has type void ( X ::*)() .

However, we are not proposing splicing constructors or destructors at the moment.

4.2.2 Limitations

Splicers can appear in many contexts, but our implementation experience has uncovered a small set of circumstances in which a splicer must be disallowed. Mostly these are because any entity designated by a splicer can be dependent on a template argument, so any context in which the language already disallows a dependent name must also disallow a dependent splicer. It also becomes possible for the first time to have the “name” of a namespace or concept become dependent on a template argument. Our implementation experience has helped to sort through which uses of these dependent names pose no difficulties, and which must be disallowed.

This proposal places the following limitations on splicers.

Iterating over the members of a class (e.g., using std :: meta :: members_of ) allows one, for the first time, to obtain “handles” representing constructors. An immediate question arises of whether it’s possible to reify these constructors to construct objects, or even to take their address. While we are very interested in exploring these ideas, we defer their discussion to a future paper; this proposal disallows splicing a reflection of a constructor (or constructor template) in any context.

We found no satisfying answer as to how to interpret examples like the one given above. Neither did we find motivating use cases: many of the “interesting” uses for reflections of namespaces are either to introspect their members, or to pass them as template arguments - but the above example does nothing to help with introspection, and neither can namespaces be reopened within any dependent context. Rather than choose between unintuitive options for a syntax without a motivating use case, we are disallowing splicers from appearing in the opening of a namespace.

C++20 already disallowed dependent enumeration types from appearing in using-enum-declarators (as in #1), as it would otherwise force the parser to consider every subsequent identifier as possibly a member of the substituted enumeration type. We extend this limitation to splices of dependent reflections of enumeration types, and further disallow the use of dependent reflections of namespaces in using-directives (as in #2) following the same principle.

What kind of parameter is S ? If R represents a class template, then it is a non-type template parameter of deduced type, but if R represents a concept, it is a type template parameter. There is no other circumstance in the language for which it is not possible to decide at parse time whether a template parameter is a type or a non-type, and we don’t wish to introduce one for this use case.

The most obvious solution would be to introduce a concept [: R :] syntax that requires that R reflect a concept, and while this could be added going forward, we weren’t convinced of its value at this time - especially since the above can easily be rewritten:

We are resolving this ambiguity by simply disallowing a reflection of a concept, whether dependent or otherwise, from being spliced in the declaration of a template parameter (thus in the above example, the parser can assume that S is a non-type parameter).

Although we would like for splices of class members to be usable as designators in an initializer-list, we lack implementation experience with the syntax and would first like to verify that there are no issues with dependent reflections. We are very likely to propose this as an extension in a future paper.

The splicers described above all take a single object of type std :: meta :: info (described in more detail below). However, there are many cases where we don’t have a single reflection, we have a range of reflections - and we want to splice them all in one go. For that, the predecessor to this paper, [ P1240R0 ] , proposed an additional form of splicer: a range splicer.

Construct the struct-to-tuple example from above. It was demonstrated using a single splice, but it would be simpler if we had a range splice:

template <typename T> constexpr auto struct_to_tuple(T const& t) { constexpr auto members = nonstatic_data_members_of(^T); constexpr auto indices = []{ std::array<int, members.size()> indices; std::ranges::iota(indices, 0); return indices; }(); constexpr auto [...Is] = indices; return std::make_tuple(t.[: members[Is] :]...); } template <typename T> constexpr auto struct_to_tuple(T const& t) { constexpr auto members = nonstatic_data_members_of(^T); return std::make_tuple(t.[: ...members :]...); }

A range splice, [: ... r :] , would accept as its argument a constant range of meta :: info , r , and would behave as an unexpanded pack of splices. So the above expression

make_tuple ( t .[: ... members :]...)

would evaluate as

make_tuple ( t .[: members [ 0 ]:] , t .[: members [ 1 ]:] , ... , t .[: members [ N-1 ]:])

This is a very useful facility indeed!

However, range splicing of dependent arguments is at least an order of magnitude harder to implement than ordinary splicing. We think that not including range splicing gives us a better chance of having reflection in C++26. Especially since, as this paper’s examples demonstrate, a lot can be done without them.

Another way to work around a lack of range splicing would be to implement with_size < N >( f ) , which would behave like f ( integral_constant < size_t , 0 >{} , integral_constant < size_t , 1 >{} , ... , integral_constant < size_t , N - 1 >{}) . Which is enough for a tolerable implementation:

template < typename T > constexpr auto struct_to_tuple ( T const & t ) { constexpr auto members = nonstatic_data_members_of (^ T ) ; return with_size < members . size ()>([&]( auto ... Is ){ return std :: make_tuple ( t .[: members [ Is ] :]...) ; }) ; }

Early discussions of splice-like constructs (related to the TS design) considered using unreflexpr (...) for that purpose. [ P1240R0 ] adopted that option for expression splicing, observing that a single splicing syntax could not viably be parsed (some disambiguation is needed to distinguish types and templates). SG-7 eventually agreed to adopt the [: ... :] syntax — with disambiguating tokens such as typename where needed — which is a little lighter and more distinctive.

We propose [: and :] be single tokens rather than combinations of [ , ] , and : . Among others, it simplifies the handling of expressions like arr [[: refl ():]] . On the flip side, it requires a special rule like the one that was made to handle <:: to leave the meaning of arr [:: N ] unchanged and another one to avoid breaking a (somewhat useless) attribute specifier of the form [[ using ns :]] .

A syntax that is delimited on the left and right is useful here because spliced expressions may involve lower-precedence operators. Additionally, it’s important that the left- and right-hand delimiters are different so as to allow nested splices when that comes up.

However, there are other possibilities. For example, now that $ or @ are available in the basic source character set, we might consider those. One option that was recently brought up was @ primary-expression which would allow writing @ e for the simple identifier splices but for the more complex operations still require parenthesizing for readability. $ < expr > is somewhat natural to those of us that have used systems where $ is used to expand placeholders in document templates:

:] (with space)
refl:] refl :] refl refl
type_of(refl):] type_of(refl) :] type_of(refl)) (type_of(refl))

There are two other pieces of functionality that we will probably need syntax for in the future:

  • code injection (of whatever form), and
  • annotations (reflectable attributes, as values. [ P1887R1 ] suggested + as an annotation introducer, but + can begin an expression so another token is probably better. See also: this thread ).

So any syntax discussion needs to consider the entirety of the feature.

The prefixes typename and template are only strictly needed in some cases where the operand of the splice is a dependent expression. In our proposal, however, we only make typename optional in the same contexts where it would be optional for qualified names with dependent name qualifiers. That has the advantage to catch unfortunate errors while keeping a single rule and helping human readers parse the intended meaning of otherwise ambiguous constructs.

4.3 std :: meta :: info

The type std :: meta :: info can be defined as follows:

namespace std { namespace meta { using info = decltype (^::) ; } }

In our initial proposal a value of type std :: meta :: info can represent:

  • any (C++) type and type alias
  • any function or member function
  • any variable, static data member, or structured binding
  • any non-static data member
  • any enumerator
  • any template
  • any namespace (including the global namespace) or namespace alias
  • any object that is a permitted result of a constant expression
  • any value with structural type that is a permitted result of a constant expression
  • the null reflection (when default-constructed)

We for now restrict the space of reflectable values to those of structural type in order to meet two requirements:

  • The compiler must know how to mangle any reflectable value (i.e., when a reflection thereof is used as a template argument).
  • The compiler must know how to compare any two reflectable values, ideally without interpreting user-defined comparison operators (i.e., to implement comparison between reflections).

Values of structural types can already be used as template arguments (so implementations must already know how to mangle them), and the notion of template-argument-equivalent values defined on the class of structural types helps guarantee that & fn <^ value1 > == & fn <^ value2 > if and only if & fn < value1 > == & fn < value2 > .

Notably absent at this time are reflections of expressions. For example, one might wish to walk over the subexpressions of a function call:

template < typename T > void fn ( T ) {} void g () { constexpr auto call = ^( fn ( 42 )) ; static_assert ( template_arguments_of ( function_of ( call ))[ 0 ] == ^ int ) ; }

Previous revisions of this proposal suggested limited support for reflections of constant expressions. The introduction of side effects from constant evaluations (by this very paper), however, renders this roughly as difficult for constant expressions as it is for non-constant expressions. We instead defer all expression reflection to a future paper, and only present value and object reflection in the present proposal.

The type std :: meta :: info is a scalar type for which equality and inequality are meaningful, but for which no ordering relation is defined.

static_assert (^ int == ^ int ) ; static_assert (^ int != ^ const int ) ; static_assert (^ int != ^ int &) ; using Alias = int ; static_assert (^ int != ^ Alias ) ; static_assert (^ int == dealias (^ Alias )) ; namespace AliasNS = :: std; static_assert (^:: std != ^ AliasNS ) ; static_assert (^:: == parent_of (^:: std )) ;

When the ^ operator is followed by an id-expression , the resulting std :: meta :: info represents the entity named by the expression. Such reflections are equivalent only if they reflect the same entity.

int x; struct S { static int y; } ; static_assert (^ x == ^ x ) ; static_assert (^ x != ^ S :: y ) ; static_assert (^ S :: y == static_data_members_of (^ S )[ 0 ]) ;

Special rules apply when comparing certain kinds of reflections. A reflection of an alias compares equal to another reflection if and only if they are both aliases, alias the same type, and share the same name and scope. In particular, these rules allow e.g., fn <^ std :: string > to refer to the same instantiation across translation units.

using Alias1 = int ; using Alias2 = int ; consteval std :: meta :: info fn () { using Alias1 = int ; return ^ Alias; } static_assert (^ Alias1 == ^ Alias1 ) ; static_assert (^ Alias1 != ^ int ) ; static_assert (^ Alias1 != ^ Alias2 ) ; static_assert (^ Alias1 != fn ()) ; }

A reflection of an object (including variables) does not compare equally to a reflection of its value. Two values of different types never compare equally.

constexpr int i = 42 , j = 42 ; constexpr std :: meta :: info r = ^ i, s = ^ i; static_assert ( r == r && r == s ) ; static_assert (^ i != ^ j ) ; // 'i' and 'j' are different entities. static_assert ( value_of (^ i ) == value_of (^ j )) ; // Two equivalent values. static_assert (^ i != std :: meta :: reflect_object ( i )) // A variable is distinct from the // object it designates. static_assert (^ i != std :: meta :: reflect_value ( 42 )) ; // A reflection of an object // is not the same as its value.

Non-type template arguments of type std :: meta :: info are permitted (and frequently useful!), but since reflections represent internal compiler state while processing a single translation unit, they cannot be allowed to leak across TUs. Therefore both variables of consteval-only type , and entities specialized by a non-type template argument of consteval-only type , cannot have module or external linkage (i.e., they must have either internal or no linkage). While this can lead to some code bloat, we aren’t aware of any organic use cases for reflection that are harmed by this limitation.

A corollary of this rule is that static data members of a class cannot have consteval-only types - such members always have external linkage, and to do otherwise would be an ODR violation. Again, we aren’t aware of any affected use-cases that absolutely require this.

template < auto R > struct S {} ; int x; auto fn () { int k; return ^ k; } static auto r = ^ int ; // r has internal name linkage. S <^ x > sx; // S<^x> has internal name linkage. S < fn ()> sy; // S<^y> has internal name linkage.

The namespace std :: meta is an associated type of std :: meta :: info , which allows standard library meta functions to be invoked without explicit qualification. For example:

#include <meta> struct S {} ; std :: string name2 = std :: meta :: identifier_of (^ S ) ; // Okay. std :: string name1 = identifier_of_of (^ S ) ; // Also okay.

Default constructing or value-initializing an object of type std :: meta :: info gives it a null reflection value. A null reflection value is equal to any other null reflection value and is different from any other reflection that refers to one of the mentioned entities. For example:

#include <meta> struct S {} ; static_assert ( std :: meta :: info () == std :: meta :: info ()) ; static_assert ( std :: meta :: info () != ^ S ) ;

4.4 Metafunctions

We propose a number of metafunctions declared in namespace std :: meta to operator on reflection values. Adding metafunctions to an implementation is expected to be relatively “easy” compared to implementing the core language features described previously. However, despite offering a normal consteval C++ function interface, each on of these relies on “compiler magic” to a significant extent.

In C++23, “constant evaluation” produces pure values without observable side-effects and thus the order in which constant-evaluation occurs is immaterial. In fact, while the language is designed to permit constant evaluation to happen at compile time, an implementation is not strictly required to take advantage of that possibility.

Some of the proposed metafunctions, however, have side-effects that have an effect on the remainder of the program. For example, we provide a define_class metafunction that provides a definition for a given class. Clearly, we want the effect of calling that metafunction to be “prompt” in a lexical-order sense. For example:

#include <meta> struct S; void g () { static_assert ( is_type ( define_class (^ S, {}))) ; S s; // S should be defined at this point. }

Hence this proposal also introduces constraints on constant evaluation as follows…

First, we identify a subset of manifestly constant-evaluated expressions and conversions characterized by the fact that their evaluation must occur and must succeed in a valid C++ program: We call these plainly constant-evaluated . We require that a programmer can count on those evaluations occurring exactly once and completing at translation time.

Second, we sequence plainly constant-evaluated expressions and conversions within the lexical order. Specifically, we require that the evaluation of a plainly constant-evaluated expression or conversion occurs before the implementation checks the validity of source constructs lexically following that expression or conversion.

Those constraints are mostly intuitive, but they are a significant change to the underlying principles of the current standard in this respect.

[ P2758R1 ] (“Emitting messages at compile time”) also has to deal with side effects during constant evaluation. However, those effects (“output”) are of a slightly different nature in the sense that they can be buffered until a manifestly constant-evaluated expression/conversion has completed. “Buffering” a class type completion is not practical (e.g., because other metafunctions may well depend on the completed class type). Still, we are not aware of incompatibilities between our proposal and [ P2758R1 ] .

Earlier revisions of this proposal suggested several possible approaches to handling errors in reflection metafunctions. This question arises naturally when considering, for instance, examples like template_of (^ int ) : the argument is a reflection of a type, but that type is not a specialization of a template, so there is no valid reflected template for us to return.

Some of the possibilities that we have considered include:

  • Returning an invalid reflection (similar to NaN for floating point) which carries source location info and some useful message (i.e., the approach suggested by P1240)
  • Returning a std :: expected < std :: meta :: info, E > for some reflection-specific error type E , which carries source location info and some useful message
  • Failing to be a constant expression
  • Throwing an exception of type E , which requires a language extension for such exceptions to be catchable during constexpr evaluation

We found that we disliked (1) since there is no satisfying value that can be returned for a call like template_arguments_of (^ int ) : We could return a std :: vector < std :: meta :: info > having a single invalid reflection, but this makes for awkward error handling. The experience offered by (3) is at least consistent, but provides no immediate means for a user to “recover” from an error.

Either std :: expected or constexpr exceptions would allow for a consistent and straightforward interface. Deciding between the two, we noticed that many of usual concerns about exceptions do not apply during translation:

  • concerns about runtime performance, object file size, etc. do not exist, and
  • concerns about code evolving to add new uncaught exception types do not apply

An interesting example illustrates one reason for our preference for exceptions over std :: expected :

template < typename T > requires ( template_of (^ T ) == ^ std :: optional ) void foo () ;

If template_of returns an expected < info, E > , then foo < int > is a substitution failure — expected < T, E > is equality-comparable to T , that comparison would evaluate to false but still be a constant expression.

If template_of returns info but throws an exception, then foo < int > would cause that exception to be uncaught, which would make the comparison not a constant expression. This actually makes the constraint ill-formed - not a substitution failure. In order to have foo < int > be a substitution failure, either the constraint would have to first check that T is a template or we would have to change the language rule that requires constraints to be constant expressions (we would of course still keep the requirement that the constraint is a bool ).

Since the R2 revision of this paper, [ P3068R1 ] has proposed the introduction of constexpr exceptions. The proposal addresses hurdles like compiler modes that disable exception support, and a Clang-based implementation is underway. We believe this to be the most desirable error-handling mechanism for reflection metafunctions.

Because constexpr exceptions have not yet been adopted into the working draft, we do not specify any functions in this paper that throw exceptions. Rather, we propose that they fail to be constant expressions (i.e., case 3 above), and note that this approach will allow us to forward-compatibly add exceptions at a later time. In the interim period, implementations should have all of the information needed to issue helpful diagnostics (e.g., “ note: R does not reflect a template specialization ”) to improve the experience of writing reflection code.

There are a number of functions, both in the “core” reflection API that we intend to provide as well as converting some of the standard library type traits that can accept or return a range of std :: meta :: info .

For example:

  • template_arguments_of (^ std :: tuple < int >) is {^ int }
  • substitute (^ std :: tuple, {^ int }) is ^ std :: tuple < int >

This requires us to answer the question: how do we accept a range parameter and how do we provide a range return.

For return, we intend on returning std :: vector < std :: meta :: info > from all such APIs. This is by far the easiest for users to deal with. We definitely don’t want to return a std :: span < std :: meta :: info const > , since this requires keeping all the information in the compiler memory forever (unlike std :: vector which could free its allocation). The only other option would be a custom container type which is optimized for compile-time by being able to produce elements lazily on demand - i.e. so that nonstatic_data_members_of (^ T )[ 3 ] wouldn’t have to populate all the data members, just do enough work to be able to return the 4th one. But that adds a lot of complexity that’s probably not worth the effort.

For parameters, there are basically three options:

  • Accept std :: span < std :: meta :: info const > , which now accepts braced-init-list arguments so it’s pretty convenient in this regard.
  • Accept std :: vector < std :: meta :: info >
  • Accept any range whose type_value is std :: meta :: info .

Now, for compiler efficiency reasons, it’s definitely better to have all the arguments contiguously. So the compiler wants span (or something like it). There’s really no reason to prefer vector over span . Accepting any range would look something like this:

namespace std :: meta { template < typename R > concept reflection_range = ranges :: input_range < R > && same_as < ranges :: range_value_t < R > , info > ; template < reflection_range R = initializer_list < info >> consteval auto substitute ( info tmpl, R && args ) -> info; }

This API is more user friendly than accepting span < info const > by virtue of simply accepting more kinds of ranges. The default template argument allows for braced-init-lists to still work. Example .

Specifically, if the user is doing anything with range adaptors, they will either end up with a non-contiguous or non-sized range, which will no longer be convertible to span - so they will have to manually convert their range to a vector < info > in order to pass it to the algorithm. Because the implementation wants contiguity anyway, that conversion to vector will happen either way - so it’s just a matter of whether every call needs to do it manually or the implementation can just do it once.

For example, converting a struct to a tuple type:

only
consteval auto type_struct_to_tuple(info type) -> meta::info { return substitute( ^tuple, nonstatic_data_members_of(type) | views::transform(meta::type_of) | views::transform(meta::type_remove_cvref) | ranges::to<vector>()); } consteval auto type_struct_to_tuple(info type) -> meta::info { return substitute( ^tuple, nonstatic_data_members_of(type) | views::transform(meta::type_of) | views::transform(meta::type_remove_cvref) ); }

This shouldn’t cause much compilation overhead. Checking convertibility to span already uses Ranges machinery. And implementations can just do the right thing interally:

consteval auto __builtin_substitute ( info tmpl, info const * arg, size_t num_args ) -> info; template < reflection_range R = initializer_list < info >> consteval auto substitute ( info tmpl, R && args ) -> info { if constexpr ( ranges :: sized_range < R > && ranges :: contiguous_range < R >) { return __builtin_substitute ( tmpl, ranges :: data ( args ) , ranges :: size ( args )) ; } else { auto as_vector = ranges :: to < vector < info >>(( R &&) args ) ; return __builtin_substitute ( tmpl, as_vector . data () , as_vector . size ()) ; } }

As such, we propose that all the range-accepting algorithms accept any range.

using A = int ;

In C++ today, A and int can be used interchangeably and there is no distinction between the two types. With reflection as proposed in this paper, that will no longer be the case. ^ A yields a reflection of an alias to int , while ^ int yields a reflection of int . ^ A == ^ int evaluates to false , but there will be a way to strip aliases - so dealias (^ A ) == ^ int evaluates to true .

This opens up the question of how various other metafunctions handle aliases and it is worth going over a few examples:

using A = int ; using B = std :: unique_ptr < int > ; template < class T > using C = std :: unique_ptr < T > ;

This paper is proposing that:

  • is_type (^ A ) is true . ^ A is an alias, but it’s an alias to a type, and if this evaluated as false then everyone would have to dealias everything all the time.
  • has_template_arguments (^ B ) is false while has_template_arguments (^ C < int >) is true . Even though B is an alias to a type that itself has template arguments ( unique_ptr < int > ), B itself is simply a type alias and does not. This reflects the actual usage.
  • Meanwhile, template_arguments_of (^ C < int >) yields {^ int } while template_arguments_of (^ std :: unique_ptr < int >) yields {^ int , ^ std :: default_deleter < int >} . This is because C has its own template arguments that can be reflected on.

One of the most “obvious” abilities of reflection — retrieving the name of an entity — turns out to raise issues that aren’t obvious at all: How do we represent source text in a C++ program?

Thanks to recent work originating in SG16 (the “Unicode” study group) we can assume that all source code is ultimately representable as Unicode code points. C++ now also has types to represent UTF-8-encoded text (incl.  char8_t , u8string , and u8string_view ) and corresponding literals like u8"Hi" . Unfortunately, what can be done with those types is still limited at the time of this writing. For example,

#include <iostream> int main () { std :: cout << u8"こんにちは世界 \n " ; }

is not standard C++ because the standard output stream does not have support for UTF-8 literals.

In practice ordinary strings encoded in the “ordinary string literal encoding” (which may or may not be UTF-8) are often used. We therefore need mechanisms to produce the corresponding ordinary string types as well.

Orthogonal to the character representation is the data structure used to traffic in source text. An implementation can easily have at least three potential representations of reflected source text:

the internal representation used, e.g., in the compiler front end’s AST-like structures (persistent)

the representation of string literals in the AST (persistent)

the representation of array of character values during constant-evaluation (transient)

(some compilers might share some of those representations). For transient text during constant evaluation we’d like to use string / u8string values, but because of the limitations on non-transient allocation during constant evaluation we cannot easily transfer such types to the non-constant (i.e., run-time) environment. E.g., if identifier_of were a (consteval) metafunction returning a std :: string value, the following simple example would not work:

#include <iostream> #include <meta> int main () { int hello_world = 42 ; std :: cout << identifier_of (^ hello_world ) << " \n " ; // Doesn't work if identifier_of produces a std::string. }

We can instead return a std :: string_view or std :: u8string_view , but that has the downside that it effectively makes all results of querying source text persistent for the compilation.

For now, however, we propose that queries like identifier_of do produce “string view” results. For example:

consteval std :: string_view identifier_of ( info ) ; consteval std :: u8string_view identifier_of ( info ) ;

An alternative strategy that we considered is the introduction of a “proxy type” for source text:

namespace std :: meta { struct source_text_info { ... template < typename T > requires (^ T == dealias (^ std :: string_view ) || ^ T == dealias (^ std :: u8string_view ) || ^ T == dealias (^ std :: string ) || ^ T == dealias (^ std :: u8string )) consteval T as () ; ... } ; }

where the as <...>() member function produces a string-like type as desired. That idea was dropped, however, because it became unwieldy in actual use cases.

With a source text query like identifier_of ( refl ) it is possible that the some source characters of the result are not representable. We can then consider multiple options, including:

the query fails to evaluate,

any unrepresentable source characters are translated to a different presentation, such as universal-character-names of the form \u { hex-number } ,

any source characters not in the basic source character set are translated to a different presentation (as in (2)).

Following much discussion with SG16, we propose #1: The query fails to evaluate if the identifier cannot be represented in the ordinary string literal encoding.

Earlier revisions of this proposal (and its predecessor, [ P1240R2 ] ) included a metafunction called name_of , which we defined to return a string_view containing the “name” of the reflected entity. As the paper evolved, it became necessary to sharpen the specification of what this “name” contains. Subsequent revisions (beginning with P2996R2, presented in Tokyo) specified that name_of returns the unqualified name, whereas a new qualified_name_of would give the fully qualified name.

Most would agree that qualified_name_of (^ size_t ) might reasonably return "std::size_t" , or that qualified_name_of (^ std :: any :: reset ) could return "std::any::reset" . But what about for local variables, or members of local classes? Should inline and anonymous namespaces be rendered as a part of the qualified name? Should we standardize the spelling of such scopes, or leave it implementation defined?

The situation is possibly even less clear for unqualified names. Should cv-qualified types be rendered as const int or int const ? Should the type for a function returning a pointer be rendered as T *(*)() , T * (*)() , or T * (*)() ? Should such decisions be standardized, or left to implementations? But the real kicker is when one considers non-type template arguments, which can (and do) contain arbitrarily complex values of arbitrary structural types (along with any complete object, or subobject thereof, which has static storage duration).

The more that we tried to specify formatting behavior for just the unqualified names of arbitrary types, the more convinced we became that this did not feel like an algorithm that should be frozen in the standard library - at least, not at this time. There are just too many toggles that a programmer might reasonably want to flip (one need only look at Clang’s PrettyPrinter class for inspiration). On the other hand, it is perfectly reasonable to ask that implementations give some means of describing what it is that a reflection contains - that is exactly the purpose of the display_string_of function.

Our stance is therefore that reflection pretty printers, for now, should be left to organically develop within the ecosystem of open-source C++ libraries. To ensure that this is possible, the Clang/P2996 fork has implemented its display_string_of metafunction entirely within the library. It is capable of printing type names, value representations, template arguments, and much more. Best of all, it can be extended without modifying the compiler.

What of name_of and qualified_name_of ? As of the R5 revision of this paper, we have removed them. In their stead is identifier_of , which is only a constant expression if the name of the represented construct is an identifier, and has_identifier for checking this condition. A few other metafunctions fill in some gaps: operator_of determines the identity of an overloaded operator, and predicates like is_operator_function and is_conversion_function_template let printing libraries handle those unqualified names that are not identifiers. parent_of supports walking up the chain of functions, namespaces, and classes enclosing the declaration of an entity, thus enabling homegrown implementations of qualified_name_of . Meanwhile, the prime real estate of name_of remains available for future library extensions.

As a nice side-effect, the identifier_of model altogether dodges some contentious questions that arose during LEWG discussions in St Louis: Should asking the “name” of an anonymous entity (e.g., anonymous unions) return the empty string, or fail to be a constant expression? Since the C++ grammar requires that an identifier contain at least one character, the identifier_of function never returns an empty string: it is seen that the only possibility is to fail to be a constant expression.

Several important metafunctions, such as std :: meta :: nonstatic_data_members_of , return a std :: vector value. Unfortunately, that means that they are currently not usable in a freestanding environment, but [ P3295R0 ] currently proposes freestanding std :: vector , std :: string , and std :: allocator in constant evaluated contexts, explicitly to make the facilities proposed by this paper work in freestanding.

Here is a synopsis for the proposed library API. The functions will be explained below.

namespace std :: meta { using info = decltype (^::) ; template < typename R > concept reflection_range = /* see above */ ; // name and location consteval auto identifier_of ( info r ) -> string_view; consteval auto u8identifier_of ( info r ) -> u8string_view; consteval auto display_string_of ( info r ) -> string_view; consteval auto u8display_string_of ( info r ) -> u8string_view; consteval auto source_location_of ( info r ) -> source_location; // type queries consteval auto type_of ( info r ) -> info; consteval auto parent_of ( info r ) -> info; consteval auto dealias ( info r ) -> info; // object and value queries consteval auto object_of ( info r ) -> info; consteval auto value_of ( info r ) -> info; // template queries consteval auto template_of ( info r ) -> info; consteval auto template_arguments_of ( info r ) -> vector < info > ; // member queries consteval auto members_of ( info type_class ) -> vector < info > ; consteval auto bases_of ( info type_class ) -> vector < info > ; consteval auto static_data_members_of ( info type_class ) -> vector < info > ; consteval auto nonstatic_data_members_of ( info type_class ) -> vector < info > ; consteval auto subobjects_of ( info type_class ) -> vector < info > ; consteval auto enumerators_of ( info type_enum ) -> vector < info > ; // member access struct access_context { static consteval access_context current () noexcept ; consteval access_context () noexcept ; } ; consteval auto is_accessible ( info r, acess_context from = access_context :: current ()) ; consteval auto accessible_members_of ( info target, access_context from = access_context :: current ()) -> vector < info > ; consteval auto accessible_bases_of ( info target, info target, access_context from = access_context :: current ()) -> vector < info > ; consteval auto accessible_nonstatic_data_members_of ( info target, access_context from = access_context :: current ()) -> vector < info > ; consteval auto accessible_static_data_members_of ( info target, access_context from = access_context :: current ()) -> vector < info > ; consteval auto accessible_subobjects_of ( info target, access_context from = access_context :: current ()) -> vector < info > ; // substitute template < reflection_range R = initializer_list < info >> consteval auto can_substitute ( info templ, R && args ) -> bool ; template < reflection_range R = initializer_list < info >> consteval auto substitute ( info templ, R && args ) -> info; // reflect_invoke template < reflection_range R = initializer_list < info >> consteval auto reflect_invoke ( info target, R && args ) -> info; template < reflection_range R1 = initializer_list < info > , reflection_range R2 = initializer_list < info >> consteval auto reflect_invoke ( info target, R1 && tmpl_args, R2 && args ) -> info; // reflect expression results template < typename T > consteval auto reflect_value ( T value ) -> info; template < typename T > consteval auto reflect_object ( T & value ) -> info; template < typename T > consteval auto reflect_function ( T & value ) -> info; // extract template < typename T > consteval auto extract ( info ) -> T; // other type predicates (see the wording ) consteval auto is_public ( info r ) -> bool ; consteval auto is_protected ( info r ) -> bool ; consteval auto is_private ( info r ) -> bool ; consteval auto is_virtual ( info r ) -> bool ; consteval auto is_pure_virtual ( info entity ) -> bool ; consteval auto is_override ( info entity ) -> bool ; consteval auto is_final ( info r ) -> bool ; consteval auto is_deleted ( info entity ) -> bool ; consteval auto is_defaulted ( info entity ) -> bool ; consteval auto is_explicit ( info entity ) -> bool ; consteval auto is_noexcept ( info entity ) -> bool ; consteval auto is_bit_field ( info entity ) -> bool ; consteval auto is_enumerator ( info entity ) -> bool ; consteval auto is_const ( info r ) -> bool ; consteval auto is_volatile ( info r ) -> bool ; consteval auto is_lvalue_reference_qualified ( info r ) -> bool ; consteval auto is_rvalue_reference_qualified ( info r ) -> bool ; consteval auto has_static_storage_duration ( info r ) -> bool ; consteval auto has_thread_storage_duration ( info r ) -> bool ; consteval auto has_automatic_storage_duration ( info r ) -> bool ; consteval auto has_internal_linkage ( info r ) -> bool ; consteval auto has_module_linkage ( info r ) -> bool ; consteval auto has_external_linkage ( info r ) -> bool ; consteval auto has_linkage ( info r ) -> bool ; consteval auto is_class_member ( info entity ) -> bool ; consteval auto is_namespace_member ( info entity ) -> bool ; consteval auto is_nonstatic_data_member ( info entity ) -> bool ; consteval auto is_static_member ( info entity ) -> bool ; consteval auto is_base ( info entity ) -> bool ; consteval auto is_data_member_spec ( info r ) -> bool ; consteval auto is_namespace ( info entity ) -> bool ; consteval auto is_function ( info entity ) -> bool ; consteval auto is_variable ( info entity ) -> bool ; consteval auto is_type ( info entity ) -> bool ; consteval auto is_type_alias ( info entity ) -> bool ; consteval auto is_namespace_alias ( info entity ) -> bool ; consteval auto is_complete_type ( info entity ) -> bool ; consteval auto is_template ( info entity ) -> bool ; consteval auto is_function_template ( info entity ) -> bool ; consteval auto is_variable_template ( info entity ) -> bool ; consteval auto is_class_template ( info entity ) -> bool ; consteval auto is_alias_template ( info entity ) -> bool ; consteval auto is_conversion_function_template ( info entity ) -> bool ; consteval auto is_operator_function_template ( info entity ) -> bool ; consteval auto is_literal_operator_template ( info entity ) -> bool ; consteval auto is_constructor_template ( info entity ) -> bool ; consteval auto is_concept ( info entity ) -> bool ; consteval auto is_structured_binding ( info entity ) -> bool ; consteval auto is_value ( info entity ) -> bool ; consteval auto is_object ( info entity ) -> bool ; consteval auto has_template_arguments ( info r ) -> bool ; consteval auto has_default_member_initializer ( info r ) -> bool ; consteval auto is_special_member ( info r ) -> bool ; consteval auto is_conversion_function ( info r ) -> bool ; consteval auto is_operator_function ( info r ) -> bool ; consteval auto is_literal_operator ( info r ) -> bool ; consteval auto is_constructor ( info r ) -> bool ; consteval auto is_default_constructor ( info r ) -> bool ; consteval auto is_copy_constructor ( info r ) -> bool ; consteval auto is_move_constructor ( info r ) -> bool ; consteval auto is_assignment ( info r ) -> bool ; consteval auto is_copy_assignment ( info r ) -> bool ; consteval auto is_move_assignment ( info r ) -> bool ; consteval auto is_destructor ( info r ) -> bool ; consteval auto is_user_provided ( info r ) -> bool ; // define_class struct data_member_options_t; consteval auto data_member_spec ( info type_class, data_member_options_t options = {}) -> info; template < reflection_range R = initializer_list < info >> consteval auto define_class ( info type_class, R &&) -> info; // define_static_string consteval auto define_static_string ( string_view str ) -> const char * ; consteval auto define_static_string ( u8string_view str ) -> const char8_t * ; // data layout struct member_offsets { size_t bytes; size_t bits; constexpr auto total_bits () const -> size_t ; auto operator <=>( member_offsets const &) const = default ; } ; consteval auto offset_of ( info entity ) -> member_offsets; consteval auto size_of ( info entity ) -> size_t ; consteval auto alignment_of ( info entity ) -> size_t ; consteval auto bit_size_of ( info entity ) -> size_t ; }
namespace std :: meta { consteval auto identifier_of ( info ) -> string_view; consteval auto u8identifier_of ( info ) -> u8string_view; consteval auto display_string_of ( info ) -> string_view; consteval auto u8display_string_of ( info ) -> u8string_view; consteval auto has_identifier ( info ) -> bool ; consteval auto source_location_of ( info r ) -> source_location; }

Given a reflection r representing a language construct X whose declaration introduces an identifier, and if that identifier is representable using the ordinary string literal encoding, then identifier_of ( r ) returns a non-empty string_view containing that identifier. Otherwise, it is not a constant expression. Whether a reflected construct has an identifier can be checked with the has_identifier metafunction.

The function u8identifier_of returns the same identifier but as a u8string_view . Note that since all identifiers can be represented as UTF-8 string literals, u8identifier_of never fails to be a constant expression because of representability concerns.

Given any reflection r , display_string_of ( r ) and u8display_string_of ( r ) return an unspecified non-empty string_view and u8string_view , respectively. Implementations are encouraged to produce text that is helpful in identifying the reflected construct (note: as an exercise, the Clang implementation of this proposal implements a pretty-printing display_string_of as a non-intrinsic library function ).

Given a reflection r , source_location_of ( r ) returns an unspecified source_location . Implementations are encouraged to produce the correct source location of the item designated by the reflection.

namespace std :: meta { consteval auto type_of ( info r ) -> info; consteval auto parent_of ( info r ) -> info; consteval auto dealias ( info r ) -> info; }

If r is a reflection designating a typed entity, type_of ( r ) is a reflection designating its type. If r is already a type, type_of ( r ) is not a constant expression. This can be used to implement the C typeof feature (which works on both types and expressions and strips qualifiers):

consteval auto type_doof ( std :: meta :: info r ) -> std :: meta :: info { return type_remove_cvref ( is_type ( r ) ? r : type_of ( r )) ; } #define typeof ( e ) [: type_doof (^ e ) :]

parent_of ( r ) is a reflection designating its immediately enclosing class, function, or (possibly inline or anonymous) namespace.

If r represents an alias, dealias ( r ) represents the underlying entity. Otherwise, dealias ( r ) produces r . dealias is recursive - it strips all aliases:

using X = int ; using Y = X; static_assert ( dealias (^ int ) == ^ int ) ; static_assert ( dealias (^ X ) == ^ int ) ; static_assert ( dealias (^ Y ) == ^ int ) ;
namespace std :: meta { consteval auto object_of ( info r ) -> info; consteval auto value_of ( info r ) -> info; }

If r is a reflection of a variable denoting an object with static storage duration, then object_of ( r ) is a reflection of the object designated by the variable. If r is already a reflection of an object, object_of ( r ) is r . For all other inputs, object_of ( r ) is not a constant expression.

int x; int & y = x; static_assert (^ x != ^ y ) ; static_assert ( object_of (^ x ) == object_of (^ y )) ;

If r is a reflection of an enumerator, then value_of ( r ) is a reflection of the value of the enumerator. Otherwise, if r is a reflection of an object usable in constant expressions , then value_of ( r ) is a reflection of the value of the object. For all other inputs, value_of ( r ) is not a constant expression.

namespace std :: meta { consteval auto template_of ( info r ) -> info; consteval auto template_arguments_of ( info r ) -> vector < info > ; }

If r is a reflection designating a specialization of some template, then template_of ( r ) is a reflection of that template and template_arguments_of ( r ) is a vector of the reflections of the template arguments. In other words, the preconditions on both is that has_template_arguments ( r ) is true .

std :: vector < int > v = { 1 , 2 , 3 } ; static_assert ( template_of ( type_of (^ v )) == ^ std :: vector ) ; static_assert ( template_arguments_of ( type_of (^ v ))[ 0 ] == ^ int ) ;
namespace std :: meta { consteval auto members_of ( info type_class ) -> vector < info > ; consteval auto bases_of ( info type_class ) -> vector < info > ; consteval auto static_data_members_of ( info type_class ) -> vector < info > ; consteval auto nonstatic_data_members_of ( info type_class ) -> vector < info > ; consteval auto subobjects_of ( info type_class ) -> vector < info > { auto subobjects = bases_of ( type_class ) ; subobjects . append_range ( nonstatic_data_members_of ( type_class )) ; return subobjects; } consteval auto enumerators_of ( info type_enum ) -> vector < info > ; }

The template members_of returns a vector of reflections representing the direct members of the class type represented by its first argument. Any non-static data members appear in declaration order within that vector. Anonymous unions appear as a non-static data member of corresponding union type. Reflections of structured bindings shall not appear in the returned vector.

The template bases_of returns the direct base classes of the class type represented by its first argument, in declaration order.

static_data_members_of and nonstatic_data_members_of return reflections of the static and non-static data members, in order, respectively.

subobjects_of returns the base class subobjects and the non-static data members of a type, in declaration order. Note that the term subobject also includes array elements , which we are excluding here. Such reflections would currently be of minimal use since you could not splice them with access (e.g.  arr .[: elem :] is not supported), so would need some more thought first.

enumerators_of returns the enumerator constants of the indicated enumeration type in declaration order.

namespace std :: meta { struct access_context { static consteval access_context current () noexcept ; consteval access_context () noexcept ; } ; consteval auto is_accessible ( info target, access_context from = {}) -> bool ; consteval auto accessible_members_of ( info target, access_context from = {}) -> vector < info > ; consteval auto accessible_bases_of ( info target, access_context from = {}) -> vector < info > ; consteval auto accessible_static_data_members_of ( info target, access_context from = {}) -> vector < info > ; consteval auto accessible_nonstatic_data_members_of ( info target, access_context from = {}) -> vector < info > ; consteval auto accessible_subobjects_of ( info target, access_context from = {}) -> vector < info > ; }

The access_context type acts as a pass-key for the purposes of checking access control. Construction with access_context :: current () stores the current context - access checking will be done from the context from which the access_context was originally created.

Each function named accessible_meow_of returns the result of meow_of filtered on is_accessible . If from is not specified, the default argument captures the current access context of the caller via the default argument. Each function also provides an overload whereby target and from may be specified as distinct arguments.

class C { int k; static_assert ( is_accessible (^ C :: k )) ; // ok: context is 'C'. static auto make_context () { return std :: meta :: access_context :: current () ; } } // by default, the context is going to be from global scope // which does not have access to C::k // but once we acquire the access context from C, that is proof that // we have access, so we can get a reflection to C::k static_assert ( accessible_subobjects_of (^ C ). size () == 0 ) ; static_assert ( accessible_subobjects_of (^ C, C :: make_context ()). size () == 1 ) ;

Unlike previous versions of this API (see [ P2996R4 ] ), the only way to gain access to protected or private members using the access_context -based API presented here is to have acquired an access_token from a context which has such access. This satisfies the requirements for an API that does not subvert access control and whose usage can be easily grepped.

namespace std :: meta { template < reflection_range R = initializer_list < info >> consteval auto can_substitute ( info templ, R && args ) -> bool ; template < reflection_range R = initializer_list < info >> consteval auto substitute ( info templ, R && args ) -> info; }

Given a reflection for a template and reflections for template arguments that match that template, substitute returns a reflection for the entity obtained by substituting the given arguments in the template. If the template is a concept template, the result is a reflection of a constant of type bool .

constexpr auto r = substitute (^ std :: vector, std :: vector {^ int }) ; using T = [: r :] ; // Ok, T is std::vector<int>

This process might kick off instantiations outside the immediate context, which can lead to the program being ill-formed.

Note that the template is only substituted, not instantiated. For example:

template < typename T > struct S { typename T :: X x; } ; constexpr auto r = substitute (^ S, std :: vector {^ int }) ; // Okay. typename [: r :] si; // Error: T::X is invalid for T = int.

can_substitute ( templ, args ) simply checks if the substitution can succeed (with the same caveat about instantiations outside of the immediate context). If can_substitute ( templ, args ) is false , then substitute ( templ, args ) will be ill-formed.

namespace std :: meta { template < reflection_range R = initializer_list < info >> consteval auto reflect_invoke ( info target, R && args ) -> info; template < reflection_range R1 = initializer_list < info > , reflection_range R2 = initializer_list < info >> consteval auto reflect_invoke ( info target, R1 && tmpl_args, R2 && args ) -> info; }

These metafunctions produce a reflection of the result of a call expression.

For the first overload: Letting F be the entity represented by target , and A 0 , A 1 , ... , A N be the sequence of entities represented by the values held by args : if the expression F ( A 0 , A 1 , ... , A N ) is a well-formed constant expression evaluating to a structural type that is not void , and if every value in args is a reflection of a value or object usable in constant expressions, then reflect_invoke ( target, args ) evaluates to a reflection of the result of F ( A 0 , A 1 , ... , A N ) . For all other invocations, reflect_invoke ( target, args ) is not a constant expression.

The second overload behaves the same as the first overload, except instead of evaluating F ( A 0 , A 1 , ... , A N ) , we require that F be a reflection of a template and evaluate F < T 0 , T 1 , ... , T M >( A 0 , A 1 , ... , A N ) . This allows evaluating reflect_invoke (^ std :: get, { std :: meta :: reflect_value ( 0 )} , { e }) to evaluate to, approximately, ^ std :: get < 0 >([: e :]) .

If the returned reflection is of a value (rather than an object), the type of the reflected value is the cv-qualified (de-aliased) type of what’s returned by the function.

A few possible extensions for reflect_invoke have been discussed among the authors. Given the advent of constant evaluations with side-effects, it may be worth allowing void -returning functions, but this would require some representation of “a returned value of type void ”. Construction of runtime call expressions is another exciting possibility. Both extensions require more thought and implementation experience, and we are not proposing either at this time.

namespace std :: meta { template < typename T > consteval auto reflect_value ( T expr ) -> info; template < typename T > consteval auto reflect_object ( T & expr ) -> info; template < typename T > consteval auto reflect_function ( T & expr ) -> info; }

These metafunctions produce a reflection of the result from evaluating the provided expression. One of the most common use-cases for such reflections is to specify the template arguments with which to build a specialization using std :: meta :: substitute .

reflect_value ( expr ) produces a reflection of the value computed by an lvalue-to-rvalue conversion on expr . The type of the reflected value is the cv-unqualified (de-aliased) type of expr . The result needs to be a permitted result of a constant expression, and T cannot be of reference type.

reflect_object ( expr ) produces a reflection of the object designated by expr . This is frequently used to obtain a reflection of a subobject, which might then be used as a template argument for a non-type template parameter of reference type.

reflect_function ( expr ) produces a reflection of the function designated by expr . It can be useful for reflecting on the properties of a function for which only a reference is available.

namespace std :: meta { template < typename T > consteval auto extract ( info ) -> T; }

If r is a reflection for a value of type T , extract < T >( r ) is a prvalue whose evaluation computes the reflected value.

If r is a reflection for an object of non-reference type T , extract < T &>( r ) and extract < T const &>( r ) are lvalues referring to that object. If the object is usable in constant expressions [expr.const], extract < T >( r ) evaluates to its value.

If r is a reflection for an object of reference type T usable in constant-expressions, extract < T >( r ) evaluates to that reference.

If r is a reflection for a function of type F , extract < F *>( r ) evaluates to a pointer to that function.

If r is a reflection for a non-static member function and T is the type for a pointer to the reflected member function, extract < T >( r ) evaluates to a pointer to the member function.

If r is a reflection for an enumerator constant of type E , extract < E >( r ) evaluates to the value of that enumerator.

If r is a reflection for a non-bit-field non-reference non-static member of type M in a class C , extract < M C ::*>( r ) is the pointer-to-member value for that non-static member.

For other reflection values r , extrace < T >( r ) is ill-formed.

The function template extract may feel similar to splicers, but unlike splicers it does not require its operand to be a constant-expression itself. Also unlike splicers, it requires knowledge of the type associated with the entity represented by its operand.

namespace std :: meta { struct data_member_options_t { struct name_type { template < typename T > requires constructible_from < u8string, T > consteval name_type ( T &&) ; template < typename T > requires constructible_from < string, T > consteval name_type ( T &&) ; } ; optional < name_type > name; optional < int > alignment; optional < int > width; bool no_unique_address = false ; } ; consteval auto data_member_spec ( info type, data_member_options_t options = {}) -> info; template < reflection_range R = initializer_list < info >> consteval auto define_class ( info type_class, R &&) -> info; }

data_member_spec returns a reflection of a description of the declaration of a data member of given type. Optional alignment, bit-field-width, and name can be provided as well. An inner class name_type , which may be implicitly constructed from any of several “string-like” types (e.g., string_view , u8string_view , char8_t [] , char_t [] ), is used to represent the name. If a name is provided, it must be a valid identifier when interpreted as a sequence of code-units. Otherwise, the name of the data member is unspecified.

define_class takes the reflection of an incomplete class/struct/union type and a range of reflections of data member descriptions and completes the given class type with data members as described (in the given order). The given reflection is returned. For now, only data member reflections are supported (via data_member_spec ) but the API takes in a range of info anticipating expanding this in the near future.

union U; static_assert ( is_type ( define_class (^ U, { data_member_spec (^ int ) , data_member_spec (^ char ) , data_member_spec (^ double ) , }))) ; // U is now defined to the equivalent of // union U { // int _0 ; // char _1 ; // double _2 ; // }; template < typename T > struct S; constexpr auto s_int_refl = define_class (^ S < int > , { data_member_spec (^ int , {. name = "i" , . alignment = 64 }) , data_member_spec (^ int , {. name = u8"こんにち" }) , }) ; // S<int> is now defined to the equivalent of // template<> struct S<int> { // alignas(64) int i; // int こんにち; // };

When defining a union , if one of the alternatives has a non-trivial destructor, the defined union will still have a destructor provided - that simply does nothing. This allows implementing variant without having to further extend support in define_class for member functions.

If type_class is a reflection of a type that already has a definition, or which is in the process of being defined, the call to define_class is not a constant expression.

namespace std :: meta { consteval auto define_static_string ( string_view str ) -> const char * ; consteval auto define_static_string ( u8string_view str ) -> const char8_t * ; }

Given a string_view or u8string_view , returns a pointer to an array of characters containing the contents of str followed by a null terminator. The array object has static storage duration, is not a subobject of a string literal object, and is usable in constant expressions; a pointer to such an object meets the requirements for use as a non-type template argument.

template < const char * P > struct C { } ; const char msg [] = "strongly in favor" ; // just an idea.. C < msg > c1; // ok C < "nope" > c2; // ill-formed C < define_static_string ( "yay" )> c3; // ok

In the absence of general support for non-transient constexpr allocation, such a facility is essential to building utilities like pretty printers.

An example of such an interface might be built as follow:

template < std :: meta :: info R > requires is_value ( R ) consteval auto render () -> std :: string; template < std :: meta :: info R > requires is_type ( R ) consteval auto render () -> std :: string; template < std :: meta :: info R > requires is_variable ( R ) consteval auto render () -> std :: string; // ... template < std :: meta :: info R > consteval auto pretty_print () -> std :: string_view { return define_static_string ( render < R >()) ; }

This strategy lies at the core of how the Clang/P2996 fork builds its example implementation of the display_string_of metafunction.

namespace std :: meta { struct member_offsets { size_t bytes; size_t bits; constexpr auto total_bits () const -> size_t { return CHAR_BIT * bytes + bits; } auto operator <=>( member_offsets const &) const = default ; } ; consteval auto offset_of ( info entity ) -> member_offsets; consteval auto size_of ( info entity ) -> size_t ; consteval auto alignment_of ( info entity ) -> size_t ; consteval auto bit_size_of ( info entity ) -> size_t ; }

These are generalized versions of some facilities we already have in the language.

  • offset_of takes a reflection of a non-static data member or a base class subobject and returns the offset of it - in bytes and then leftover bits (always between 0 and 7 inclusive).
  • size_of takes the reflection of a type, object, variable, non-static data member, or base class subobject and returns its size.
  • alignment_of takes the reflection of a type, non-static data member, or base class subobject and returns its alignment.
  • bit_size_of gives the size of a base class subobject or non-static data member, except in bits.
struct Msg { uint64_t a : 10 ; uint64_t b : 8 ; uint64_t c : 25 ; uint64_t d : 21 ; } ; static_assert ( offset_of (^ Msg :: a ) == member_offsets { 0 , 0 }) ; static_assert ( offset_of (^ Msg :: b ) == member_offsets { 1 , 2 }) ; static_assert ( offset_of (^ Msg :: c ) == member_offsets { 2 , 2 }) ; static_assert ( offset_of (^ Msg :: d ) == member_offsets { 5 , 3 }) ; static_assert ( bit_size_of (^ Msg :: a ) == 10 ) ; static_assert ( bit_size_of (^ Msg :: b ) == 8 ) ; static_assert ( bit_size_of (^ Msg :: c ) == 25 ) ; static_assert ( bit_size_of (^ Msg :: d ) == 21 ) ; static_assert ( offset_of (^ Msg :: a ). total_bits () == 0 ) ; static_assert ( offset_of (^ Msg :: b ). total_bits () == 10 ) ; static_assert ( offset_of (^ Msg :: c ). total_bits () == 18 ) ; static_assert ( offset_of (^ Msg :: d ). total_bits () == 43 ) ;

There is a question of whether all the type traits should be provided in std :: meta . For instance, a few examples in this paper use std :: meta :: type_remove_cvref ( t ) as if that exists. Technically, the functionality isn’t strictly necessary - since it can be provided indirectly:

std::meta::type_remove_cvref(type) std::meta::substitute(^std::remove_cvref_t, {type})
std::meta::type_is_const(type) std::meta::extract<bool>(std::meta::substitute(^std::is_const_v, {type}))

Having std :: meta :: meow for every trait std :: meow is more straightforward and will likely be faster to compile, though means we will have a much larger library API. There are quite a few traits in 21 [meta] - but it should be easy enough to specify all of them. So we’re doing it.

Now, one thing that came up is that the straightforward thing we want to do is to simply add a std :: meta :: meow for every trait std :: meow and word it appropriately. That’s what the current wording in this revision does. However, we’ve run into a conflict. The standard library type traits are all type traits - they only accept types. As such, their names are simply things like std :: is_pointer , std :: is_const , std :: is_lvalue_reference , and so forth. Renaming it to std :: type_is_pointer , for instance, would be a waste of characters since there’s nothing else the argument could be save for a type. But this is no longer the case. Consider std :: meta :: is_function ( e ) , which is currently actually specified twice in our wording having two different meanings:

  • A consteval function equivalent of the type trait std :: is_function < T > , such that std :: meta :: is_function ( e ) mandates that e reflect a type and checks if that type is a function type. This is the same category of type trait as the ones mentioned above.
  • A new kind of reflection query std :: meta :: is_function ( e ) which asks if e is the reflection of a function (as opposed to a type or a namespace or a template, etc.). This is the same category of query as std :: meta :: is_template or std :: meta :: is_concept or std :: meta :: is_namespace .

Both of these are useful, yet they mean different things entirely - the first is ill-formed when passed a reflection of a function (as opposed to a function type), and the second would simply answer false for the reflection of any type (function type or otherwise). So what do we do?

Probably the most straightforward choice would be to either prefix or suffix all of the type traits with _type . We think prefix is a little bit better because it groups all the type traits together and perhaps make it clearer that the argument(s) must be types. That is: std :: is_pointer < T > because std :: meta :: type_is_pointer (^ T ) , std :: is_arithmetic < T > becomes std :: meta :: type_is_arithmetic (^ T ) , and so forth. The advantage of this approach is that it very likely just works, also opening the door to making a more general std :: meta :: is_const ( e ) that checks not just if e is a const -qualified type but also if it’s a const -qualified object or a const -qualified member, etc. The disadvantage is that the suffixed names would not be familiar - we’re much more familiar with the name is_copy_constructible than we would be with type_is_copy_constructible .

That said, it’s not too much added mental overhead to remember type_is_copy_constructible and this avoids have to remember which type traits have the suffix and which don’t. Not to mention that many of the type traits read as if they would accept objects just fine (e.g.  is_trivially_copyable ). So we propose that simply all the type traits be suffixed with * _type .

Static reflection invariably brings new ways to violate ODR.

Two translation units including cls . h can generate different definitions of Cls :: odr_violator () based on whether an odd or even number of declarations have been imported from std . Branching on the members of a namespace is dangerous because namespaces may be redeclared and reopened: the set of contained declarations can differ between program points.

The creative programmer will find no difficulty coming up with other predicates which would be similarly dangerous if substituted into the same if constexpr condition: for instance, given a branch on is_complete_type (^ T ) , if one translation unit #include s a forward declaration of T , another #include s a complete definition of T , and they both afterwards #include "cls.h" , the result will be an ODR violation.

Additional papers are already in flight proposing additional metafunctions that pose similar dangers. For instance, [ P3096R1 ] proposes the parameters_of metafunction. This feature is important for generating language bindings (e.g., Python, JavaScript), but since parameter names can differ between declarations, it would be dangerous for a member function defined in a header file to branch on the name of a parameter.

These cases are not difficult to identify: Given an entity E and two program points P1 and P2 from which a reflection of E may be optained, it is unsafe to branch runtime code generation on any property of E (e.g., namespace members, parameter names, completeness of a class) that can be modified between P1 and P2 . Worth noting as well, these sharp edges are not unique (or new) to reflection: It is already possible to build an ODR trap based on the completeness of a class using C++23.

Education and training are important to help C++ users avoid such sharp edges, but we do not find them sufficiently concerning to give pause to our enthusiasm for the features proposed by this paper.

5 Proposed Wording

[ Editor's note: Throughout the wording, we say that a reflection (an object of type std::meta::info ) represents some source construct, while splicing that reflection designates that source construct. For instance, ^int represents the type int and [: ^int :] designates the type int . ]

5.1 Language

Modify the wording for phases 7-8 of 5.2 [lex.phases] as follows:

7 Whitespace characters separating tokens are no longer significant. Each preprocessing token is converted into a token (5.6). The resulting tokens constitute a translation unit and are syntactically and semantically analyzed and translated. Plainly constant-evaluated expressions ([expr.const]) appearing outside template declarations are evaluated in lexical order. Diagnosable rules ( 4.1.1 [intro.compliance.general] ) that apply to constructs whose syntactic end point occurs lexically after the syntactic end point of a plainly constant-evaluated expression X are considered in a context where X has been evaluated. […] 8 […] All the required instantiations are performed to produce instantiation units. Plainly constant-evaluated expressions ([expr.const]) appearing in those instantiation units are evaluated in lexical order as part of the instantiation process. Diagnosable rules ( 4.1.1 [intro.compliance.general] ) that apply to constructs whose syntactic end point occurs lexically after the syntactic end point of a plainly constant-evaluated expression X are considered in a context where X has been evaluated. […]

Add a bullet after 5.4 [lex.pptoken] bullet (3.2):

… — Otherwise, if the next three characters are <:: and the subsequent character is neither : nor > , the < is treated as a preprocessing token by itself and not as the first character of the alternative token <: . — Otherwise, if the next three characters are [:: and the subsequent character is not : or if the next three characters are [:> , the [ is treated as a preprocessing token by itself and not as the first character of the preprocessing token [: . …

Change the grammar for operator-or-punctuator in paragraph 1 of 5.12 [lex.operators] to include splicer delimiters:

operator-or-punctuator : one of { } [ ] ( ) [: :] <: :> <% %> ; : ... ? :: . .* -> ->* ~ ! + - * / % ^ & | = += -= *= /= %= ^= &= |= == != < > <= >= <=> && || << >> <<= >>= ++ -- , and or xor not bitand bitor compl and_eq or_eq xor_eq not_eq

Modify paragraph 4.1 to cover splicing of functions:

(4.1) A function is named by an expression or conversion if it is the selected member of an overload set ([basic.lookup], [over.match], [over.over]) in an overload resolution performed as part of forming that expression or conversion, or if it is designated by a splice-expression ([expr.prim.splice]), unless it is a pure virtual function and either the expression is not an id-expression naming the function with an explicitly qualified name or the expression forms a pointer to member ([expr.unary.op]).

Modify the first sentence of paragraph 5 to cover splicing of variables:

5 A variable is named by an expression if the expression is an id-expression or splice-expression ([expr.prim.splice]) that designates it.

Modify paragraph 6 to cover splicing of structured bindings:

6 A structured binding is odr-used if it appears as a potentially-evaluated expression , or if a reflection of it is the operand of a potentially-evaluated splice-expression ([expr.prim.splice]) .

Prepend before paragraph 15 of 6.3 [basic.def.odr] :

15pre If a class C is defined in a translation unit with a call to a specialization of std :: meta :: define_class , every definition of that class shall be the result of a call to the same specialization; and for every reflection in the range of reflections describing its class members, every other such call shall have a corresponding value, occupying the same position in its respective range, to which the reflection compares equal. 15 Otherwise, for For any definable item D with definitions …

Modify the first bullet of paragraph 3 of 6.5.4 [basic.lookup.argdep] as follows:

3 … Any typedef-name s and using-declaration s used to specify the types do not contribute to this set. The set of entities is determined in the following way: (3.1) If T is std :: meta :: info , its associated set of entities is the singleton containing the function std :: meta :: is_type . If T is a any other fundamental type, its associated set of entities is empty. (3.2) If T is a class type …

FIXME. Have to handle splices in here, because they’re not actually “component names”. Now splice-namespace-qualifier is only a namespace too.

Extend 6.5.5.1 [basic.lookup.qual.general] /1-2 to cover splice-namespace-qualifier :

1 Lookup of an identifier followed by a ​ :: ​ scope resolution operator considers only namespaces, types, and templates whose specializations are types. If a name, template-id , or computed-type-specifier , or splice-namespace-qualifier is followed by a ​ :: ​, it shall designate a namespace, class, enumeration, or dependent type, and the ​::​ is never interpreted as a complete nested-name-specifier. 2 A member-qualified name is the (unique) component name ([expr.prim.id.unqual]), if any, of (2.1) an unqualified-id or (2.2) a nested-name-specifier of the form type-name :: or , namespace-name :: , or splice-namespace-qualifier :: in the id-expression of a class member access expression ([expr.ref]). […]

Add a bullet to paragraph 4, and renumber accordingly:

4 An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. The name of an entity that belongs to a namespace scope that has not been given internal linkage above and that is the name of * (4.1) a variable; or … has its linkage determined as follows: (4.7) if the enclosing namespace has internal linkage, the name has internal linkage; (4.8) otherwise, if the declaration of the name is attached to a named module ([module.unit]) and is not exported ([module.interface]), the name has module linkage; (4.9) otherwise, if the declaration introduces a variable whose type is a consteval-only type ([basic.types.general]) or a class template specialization where one or more non-type template arguments are of consteval-only type, the name has internal linkage. (4.10) otherwise, the name has external linkage.

Change the first sentence in paragraph 9 of 6.8.1 [basic.types.general] as follows:

9 Arithmetic types (6.8.2), enumeration types, pointer types, pointer-to-member types (6.8.4), std :: meta :: info , std :: nullptr_t , and cv-qualified (6.8.5) versions of these types are collectively called scalar types.

Add a new paragraph at the end of 6.8.1 [basic.types.general] as follows:

* A consteval-only type is one of the following: std :: meta :: info , or a pointer or reference to a consteval-only type, or an (possibly multi-dimensional) array of a consteval-only type, or a class type with a base class or non-static data member of consteval-only type, or a function type with a return type or parameter type of consteval-only type, or a pointer-to-member type to a class C of type M where either C or M is a consteval-only type. An object of consteval-only type shall either end its lifetime during the evaluation of a manifestly constant-evaluated expression or conversion ( 7.7 [expr.const] ), or be a constexpr variable for which every expression that names the variable is within an immediate function context. * Consteval-only types may not be used to declare a static data member of a class having module or external linkage. Furthermore, specializations of a class template having a non-type template argument of consteval-only type may not be used to declare a static data member of a class having module or external linkage.

Add a new paragraph before the last paragraph of 6.8.2 [basic.fundamental] as follows:

17 - 1 A value of type std :: meta :: info is called a reflection . There exists a unique null reflection ; every other reflection is a representation of a value with structural type ([temp.param]), an object with static storage duration, a variable, a structured binding, a function, an enumerator, a type, a class member, a bit-field, a primary class template, function template, primary variable template, alias template, or concept, a namespace, an alias of a type or namespace, a base class specifier, or a description of the declaration of a non-static data member. An expression convertible to a reflection is said to represent the corresponding entity, variable, alias, base class specifier, or description of the declaration of a non-static data member.

Change the grammar for primary-expression in 7.5 [expr.prim] as follows:

primary-expression : literal this ( expression ) id-expression lambda-expression fold-expression requires-expression + splice-expression

FIXME: The wording here, and usage throughout.

Add a new grammar term for convenience:

splice-specifier : [: constant-expression :] 1 The constant-expression of a splice-specifier shall be a converted constant expression ([expr.const]) contextually convertible to std :: meta :: info . 2 Let E be the value of the converted constant-expression . The splice-specifier designates what E represents. 3 A splice-specifier is dependent if the converted constant-expression is value-dependent.

Add a carve-out for reflection in 7.5.4.1 [expr.prim.id.general] /4:

4 An id-expression that denotes a non-static data member or implicit object member function of a class can only be used: (4.1) as part of a class member access (after any implicit transformation (see above)) in which the object expression refers to the member’s class or a class derived from that class, or (4.2) as an operand to the reflection operator ([expr.reflect]), or (4.3) to form a pointer to member ([expr.unary.op]), or (4.4) if that id-expression denotes a non-static data member and it appears in an unevaluated operand.

Add a production to the grammar for nested-name-specifier as follows:

nested-name-specifier : :: type-name :: namespace-name :: + splice-namespace-qualifier :: computed-type-specifier :: nested-name-specifier identifier :: nested-name-specifier template opt simple-template-id :: + + splice-namespace-qualifier : + splice-specifier

Add a new paragraph restricting splice-namespace-qualifier , and renumber accordingly:

0 The splice-specifier of a splice-namespace-qualifier shall designate a namespace or namespace alias. 1 The component names of a qualified-id are […]

Clarify that a splice cannot appear in a declarative nested-name-specifier :

2 A nested-name-specifier is declarative if it is part of a class-head-name , an enum-head-name , a qualified-id that is the id-expression of a declarator-id , or a declarative nested-name-specifier . A declarative nested-name-specifier shall not have a decltype-specifier or a splice-specifier . A declaration that uses a declarative nested-name-specifier shall be a friend declaration or inhabit a scope that contains the entity being redeclared or specialized.

Extend the next paragraph to also cover splices, and prefer the verb “designate” over “nominate”:

4 The nested-name-specifier ​ :: ​ nominates designates the global namespace. A nested-name-specifier with a computed-type-specifier nominates designates the type denoted by the computed-type-specifier , which shall be a class or enumeration type. A nested-name-specifier with a splice-namespace-qualifier nominates designates the same namespace or namespace alias as the splice-namespace-qualifier . If a nested-name-specifier N is declarative and has a simple-template-id with a template argument list A that involves a template parameter, let T be the template nominated designated by N without A . T shall be a class template. …

Add a new subsection of 7.5 [expr.prim] following 7.5.7 [expr.prim.req]

Expression Splicing [expr.prim.splice] FIXME: text for the template version. splice-expression : splice-specifier template splice-specifier < template-argument-list opt > 1 For a splice-expression of the form splice-specifier , let E be the value of the converted constant-expression of the splice-specifier . (1.1) If E is a reflection for an object, a function which is not a constructor or destructor, a non-static data member that is not an unnamed bit-field, or a structured binding, the expression is an lvalue denoting the reflected entity. (1.2) Otherwise, if E is a reflection for a variable or a structured binding, the expression is an lvalue denoting the object designated by the reflected entity. (1.3) Otherwise, E shall be a reflection of a value or an enumerator, and the expression is a prvalue whose evaluation computes the reflected value. [  Note 1: Access checking of class members occurs during name lookup, and therefore does not pertain to splicing. —  end note  ]

Add a production to postfix-expression for splices in member access expressions:

[1]{.pnum} Postfix expressions group left-to-right. postfix-expression : ... postfix-expression . template opt id-expression + postfix-expression . template opt splice-expression postfix-expression -> template opt id-expression + postfix-expression -> template opt splice-expression

Modify paragraph 1 to account for splices in member access expressions:

1 A postfix expression followed by a dot . or an arrow -> , optionally followed by the keyword template, and then followed by an id-expression or a splice-expression , is a postfix expression. [  Note 1: If the keyword template is used, the following unqualified name is considered to refer to a template ([temp.names]). If a simple-template-id results and is followed by a ​ :: ​, the id-expression or splice-expression is a qualified-id. —  end note  ]

Modify paragraph 2 to account for splices in member access expressions:

2 For the first option, if the dot is followed by an id-expression names or splice-expression designating a static member or an enumerator, the first expression is a discarded-value expression ([expr.context]); if the id-expression or splice-expression designates names a non-static data member, the first expression shall be a glvalue. For the second option (arrow), the first expression shall be a prvalue having pointer type. The expression E1 -> E2 is converted to the equivalent form (*( E1 )). E2 ; the remainder of [expr.ref] will address only the first option (dot).

Modify paragraph 3 to account for splices in member access expressions:

3 The postfix expression before the dot is evaluated; the result of that evaluation, together with the id-expression or splice-expression , determines the result of the entire postfix expression.

Modify paragraph 4 to account for splices in member access expressions:

4 Abbreviating postfix-expression . id-expression postfix-expression . EXPR , where EXPR is the id-expression or splice-expression following the dot, as E1 . E2 , E1 is called the object expression . If the object expression is of scalar type, E2 shall name designate the pseudo-destructor of that same type (ignoring cv-qualifications) and E1 . E2 is a prvalue of type “function of () returning void ”.

Change 7.6.2.1 [expr.unary.general] paragraph 1 to add productions for the new operator:

1 Expressions with unary operators group right-to-left. unary-expression : ... delete-expression + reflect-expression

Add a new subsection of 7.6.2 [expr.unary] following 7.6.2.9 [expr.delete]

The Reflection Operator [expr.reflect] FIXME: template-name and id-expression can both refer to template names, have to handle this better. See wording in the template argument parsing section. reflect-expression : ^ :: ^ nested-name-specifier opt namespace-name ^ nested-name-specifier opt template-name ^ nested-name-specifier opt concept-name ^ type-id ^ id-expression 1 The unary ^ operator, called the reflection operator , yields a prvalue of type std :: meta :: info ( 6.8.2 [basic.fundamental] ). 2 A reflect-expression is parsed as the longest possible sequence of tokens that could syntactically form a reflect-expression . 3 [  Example 1: static_assert(is_type(^int())); // ^ applies to the type-id "int()" template<bool> struct X {}; bool operator<(std::meta::info, X<false>); consteval void g(std::meta::info r, X<false> xv) { r == ^int && true; // error: ^ applies to the type-id "int&&" r == ^int & true; // error: ^ applies to the type-id "int&" r == (^int) && true; // OK r == ^int &&&& true; // OK ^X < xv; // error: < starts template argument list (^X) < xv; // OK } —  end example  ] 4 When applied to :: , the reflection operator produces a reflection for the global namespace. When applied to a namespace-name , the reflection operator produces a reflection for the indicated namespace or namespace alias. 5 When applied to a template-name , the reflection operator produces a reflection for the indicated template. 6 When applied to a concept-name , the reflection operator produces a reflection for the indicated concept. 7 When applied to a typedef-name , the reflection operator produces a reflection of the indicated typedef-name . When applied to any other type-id , the reflection operator produces a reflection of the indicated type. 8 When applied to an id-expression , the reflection operator produces a reflection as follows: (8.1) When applied to an enumerator, the reflection operator produces a reflection of the enumerator designated by the operand. (8.2) Otherwise, when applied to an overload set S , if the assignment of S to an invented variable of type const auto ( 9.2.9.7.2 [dcl.type.auto.deduct] ) would select a unique candidate function F from S , the result is a reflection of F . Otherwise, the expression ^ S is ill-formed. (8.3) Otherwise, when applied to one of (8.3.1) a non-type template parameter of non-class and non-reference type or (8.3.2) a pack-index-expression of non-class and non-reference type the reflection operator produces a reflection of the value computed by the operand. (8.4) Otherwise, the reflection operator produces a reflection of the variable, function, or non-static member designated by the operand. The id-expression is not evaluated. [  Example 2: template < typename T > void fn () requires (^ T != ^ int ) ; template < typename T > void fn () requires (^ T == ^ int ) ; template < typename T > void fn () requires ( sizeof ( T ) == sizeof ( int )) ; constexpr auto R = ^ fn < char > ; // OK constexpr auto S = ^ fn < int > ; // error: cannot reflect an overload set constexpr auto r = ^ std :: vector; // OK —  end example  ]

Extend 7.6.10 [expr.eq] /2 to also handle std :: meta :: info :

2 The converted operands shall have arithmetic, enumeration, pointer, or pointer-to-member type, or type one of the types std :: meta :: info or std​ :: ​nullptr_t . The operators == and != both yield true or false , i.e., a result of type bool . In each case below, the operands shall have the same type after the specified conversions have been applied.

Add a new paragraph between 7.6.10 [expr.eq] /5 and /6:

5 Two operands of type std​ :: ​nullptr_t or one operand of type std​ :: ​nullptr_t and the other a null pointer constant compare equal. * If both operands are of type std :: meta :: info , comparison is defined as follows: (*.1) If one operand is a null reflection value, then they compare equal if and only if the other operand is also a null reflection value. (*.2) Otherwise, if one operand represents a variable, then they compare equal if and only if the other operand represents the same variable. (*.3) Otherwise, if one operand represents a template-id referring to a specialization of an alias template, then they compare equal if and only if the other operand represents the same template-id ([temp.type]). (*.4) Otherwise, if one operand represents a namespace alias or a typedef-name , then they compare equal if and only if the other operand represents a namespace alias or typedef-name sharing the same name, declared within the same enclosing scope, and aliasing the same underlying entity. (*.5) Otherwise, if one operand represents a value, then they compare equal if and only if the other operand represents a template-argument-equivalent value ( 13.6 [temp.type] ). (*.6) Otherwise, if one operand represents an object, then they compare equal if and only if the other operand represents the same object. (*.7) Otherwise, if one operand represents an entity, then they compare equal if and only if the other operand represents the same entity. (*.8) Otherwise, if one operand represents a base class specifier, then they compare equal if and only if the other operand represents the same base class specifier. (*.9) Otherwise, both operands O 1 and O 2 represent descriptions of declarations of non-static data members: Let C 1 and C 2 be invented class types such that each C k has a single non-static data member having the properties described by O k . The operands compare equal if and only if the data members of C 1 and C 2 would share the same type, name (if any), alignment-specifiers (if any), width, and attributes. 6 If two operands compare equal, the result is true for the == operator and false for the != operator. If two operands compare unequal, the result is false for the == operator and true for the != operator. Otherwise, the result of each of the operators is unspecified.

Add a new paragraph after the definition of manifestly constant-evaluated 7.7 [expr.const] /20:

21 An expression or conversion is plainly constant-evaluated if it is: (21.1) a constant-expression , or (21.2) the condition of a constexpr if statement ( 8.5.2 [stmt.if] ), (21.3) the initializer of a constexpr ( 9.2.6 [dcl.constexpr] ) or constinit ( 9.2.7 [dcl.constinit] ) variable, or (21.4) an immediate invocation, unless it (21.4.1) results from the substitution of template parameters during template argument deduction ( 13.10.3 [temp.deduct] ), in a concept-id ( 13.3 [temp.names] ), or in a requires-expression ( 7.5.7 [expr.prim.req] ), or (21.4.2) is a manifestly constant-evaluated initializer of a variable that is neither constexpr ( 9.2.6 [dcl.constexpr] ) nor constinit ( 9.2.7 [dcl.constinit] ).

Extend the grammar for computed-type-specifier as follows:

computed-type-specifier : decltype-specifier pack-index-specifier + splice-type-specifier

Add a new subsection of 9.2.9 [dcl.type] following 9.2.9.8 [dcl.type.class.deduct] .

+ splice-type-specifier + typename opt splice-specifier 1 The typename may be omitted only within a type-only context ( 13.8.1 [temp.res.general] ). 2 The splice-specifier shall designate a type. The type designated by the splice-type-specifier is the same type designated by the splice-specifier .

Change paragraphs 6-8 of 9.4.1 [dcl.init.general] [ Editor's note: No changes are necessary for value-initialization, which already forwards to zero-initialization for scalar types ] :

6 To zero-initialize an object or reference of type T means: (6.0) if T is std :: meta :: info , the object is initialized to a null reflection value; (6.1) if T is a any other scalar type ([basic.types.general]), the object is initialized to the value obtained by converting the integer literal 0 (zero) to T ; (6.2) […] 7 To default-initialize an object of type T means: (7.1) If T is a (possibly cv-qualified) class type ([class]), […] (7.2) If T is an array type, […] (7.*) If T is std :: meta :: info , the object is zero-initialized. (7.3) Otherwise, no initialization is performed. 8 A class type T is const-default-constructible if default-initialization of T would invoke a user-provided constructor of T (not inherited from a base class) or if (8.1) […] If a program calls for the default-initialization of an object of a const-qualified type T , T shall be std :: meta :: info or a const-default-constructible class type, or array thereof. 9 To value-initialize an object of type T means: […]

Add a bullet to paragraph 9 of 9.3.4.6 [dcl.fct] to allow for reflections of abominable function types:

9 A function type with a cv-qualifier-seq or a ref-qualifier (including a type named by typedef-name ([dcl.typedef], [temp.param])) shall appear only as: (9.1) the function type for a non-static member function, (9.2) … (9.5) the type-id of a template-argument for a type-parameter ([temp.arg.type]) . , (9.6) the operand of a reflect-expression ([expr.reflect]).

Change paragraph 2 of 9.5.3 [dcl.fct.def.delete] to allow for reflections of deleted functions:

2 A program that refers to a deleted function implicitly or explicitly, other than to declare it or to use as the operand of the reflection operator , is ill-formed.

Extend the grammar for using-enum-declarator as follows:

using-enum-declaration : using enum using-enum-declarator ; using-enum-declarator : nested-name-specifier opt identifier nested-name-specifier opt simple-template-id + splice-specifier

Modify paragraph 1 of 9.7.2 [enum.udecl] as follows:

1 A using-enum-declarator that is not a splice-specifier names the set of declarations found by lookup ( 6.5.3 [basic.lookup.unqual] , 6.5.5 [basic.lookup.qual] ) for the using-enum-declarator . The using-enum-declarator shall designate a non-dependent type with a reachable enum-specifier .

Add a production to the grammar for qualified-namespace-specifier as follows:

namespace-alias : identifier namespace-alias-definition : namespace identifier = qualified-namespace-specifier qualified-namespace-specifier : nested-name-specifier opt namespace-name + splice-specifier

Add the following prior to paragraph 1, and renumber accordingly:

0 If a qualified-namespace-specifier is a splice-specifier , the splice-specifier shall designate a namespace or namespace alias; the qualified-namespace-specifier designates the same namespace or namespace alias designated by the splice-specifier . Otherwise, the qualified-namespace-specifier designates the namespace found by lookup ( 6.5.3 [basic.lookup.unqual] , 6.5.5 [basic.lookup.qual] ).

Prefer the verb “designate” for qualified-namespace-specifiers in the paragraph that immediately follows:

2 The identifier in a namespace-alias-definition becomes a namespace-alias and denotes the namespace denoted designated by the qualified-namespace-specifier .

Use qualified-namespace-specifier in the grammar for using-directive :

using-directive : - attribute-specifier-seq opt using namespace nested-name-specifier opt namespace-name + attribute-specifier-seq opt using namespace qualified-namespace-specifier

Add the following prior to the first paragraph of 9.8.4 [namespace.udir] , and renumber accordingly:

0 The qualified-namespace-specifier shall neither contain a dependent nested-name-specifier nor a dependent splice-specifier . 1 A using-directive shall not appear in class scope, but may appear in namespace scope or in block scope. […]

Prefer the verb “designate” rather than “nominate” in the notes that follow:

[  Note 2: A using-directive makes the names in the nominated designated namespace usable in the scope […]. During unqualified name lookup, the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nomindated designated namespace. —  end note  ] […] [  Note 4: A using-directive is transitive: if a scope contains a using-directive that nominates designates a namespace that itself contains using-directives , the namespaces nominated designated by those using-directives are also eligible to be considered. —  end note  ]

Add a production to the grammar for attribute-specifier as follows:

attribute-specifier : [ [ attribute-using-prefix opt attribute-list ] ] + [ [ using attribute-namespace :] ] alignment-specifier

and update the grammar for balanced token as follows:

balanced-token : ( balanced-token-seq opt ) [ balanced-token-seq opt ] { balanced-token-seq opt } - any token other than a parenthesis, a bracket, or a brace + [: balanced-token-seq opt :] + any token other than (, ), [, ], {, }, [:, or :]

Change a sentence in paragraph 4 of 9.12.1 [dcl.attr.grammar] as follows:

4 […] An attribute-specifier that contains no attribute s and no alignment-specifier has no effect. [  Note 1: That includes an attribute-specifier of the form [ [ using attribute-namespace :] ] which is thus equivalent to replacing the :] token by the two-token sequence : ] . —  end note  ] …

Add built-in operator candidates for std :: meta :: info to 12.5 [over.built] :

16 For every T , where T is a pointer-to-member type , std :: meta :: info , or std​ :: ​nullptr_t , there exist candidate operator functions of the form bool operator ==( T, T ) ; bool operator !=( T, T ) ;

Extend the last sentence of paragraph 4 to disallow splicing concepts in template parameter declarations.

4 … The concept designated by a type-constraint shall be a type concept ([temp.concept]) that is not a splice-template-name .

Modify the grammars for template-id and template-argument as follows:

+ splice-template-name : + template splice-specifier + + splice-template-argument : + splice-specifier + template-name : identifier + splice-template-name template-argument : constant-expression type-id id-expression braced-init-list + splice-template-argument

Extend paragraph 1 to cover template splicers:

The component name of a simple-template-id , template-id , or template-name that is an identifier is the first name in it. If the template-name is a splice-template-name , the splice-specifier shall designate a concept, variable template, class template, alias template, or function template that is not a constructor template or destructor template; the splice-template-name names the entity designated by the splice-specifier .

Extend paragraph 3 of 13.3 [temp.names] :

3 A < is interpreted as the delimiter of a template-argument-list if it follows a name that is not a conversion-function-id and (3.1) that follows the keyword template or a ~ after a nested-name-specifier or in a class member access expression, or (3.2) for which name lookup finds the injected-class-name of a class template or finds any declaration of a template, or (3.3) that is an unqualified name for which name lookup either finds one or more functions or finds nothing, or (3.4) that is a terminal name in a using-declarator ([namespace.udecl]), in a declarator-id ([dcl.meaning]), or in a type-only context other than a nested-name-specifier ([temp.res]). [  Note 1: If the name is an identifier, it is then interpreted as a template-name . The keyword template is used to indicate that a dependent qualified name ([temp.dep.type]) denotes a template where an expression might appear. —  end note  ] A < is also interpreted as the delimiter of a template-argument-list if it follows a splice-template-name . [  Example 1: struct X { template<std::size_t> X* alloc(); template<std::size_t> static X* adjust(); }; template<class T> void f(T* p) { T* p1 = p->alloc<200>(); // error: < means less than T* p2 = p->template alloc<200>(); // OK, < starts template argument list T::adjust<100>(); // error: < means less than T::template adjust<100>(); // OK, < starts template argument list + static constexpr auto r = ^T::adjust; + T* p3 = [:r:]<200>(); // error: < means less than + T* p4 = template [:r:]<200>(); // OK, < starts template argument list } —  end example  ]

Change paragraph 9 to allow splicing into a concept-id :

9 A concept-id is a simple-template-id where the template-name is either a concept-name or a splice-template-name whose splice-specifier designates a concept . A concept-id is a prvalue of type bool, and does not name a template specialization.

Adjust paragraph 3 of [temp.arg.general] to not apply to splice template arguments:

3 A template-argument of the form splice-specifier is interpreted as a splice-template-argument . In a template-argument that is not a splice-template-argument , an ambiguity between a type-id and an expression is resolved to a type-id , regardless of the form of the corresponding template-parameter .

Extend 13.4.2 [temp.arg.type] /1 to cover splice template arguments:

1 A template-argument for a template-parameter which is a type shall either be a type-id or a splice-template-argument whose splice-specifier designates a type .

TODO: splice-specifier shall designate a value or something.

Extend 13.4.4 [temp.arg.template] /1 to cover splice template arguments:

1 A template-argument for a template template-parameter shall be the name of a class template or an alias template, expressed as id-expression , or a splice-template-argument whose splice-specifier designates a template .

Extend template-argument-equivalent to handle std :: meta :: info :

2 Two values are template-argument-equivalent if they are of the same type and (2.1) they are of integral type and their values are the same, or (2.2) they are of floating-point type and their values are identical, or (2.3) they are of type std​ :: ​nullptr_t , or (2.*) they are of type std :: meta :: info and they compare equal, or (2.4) they are of enumeration type and their values are the same, or (2.5) […]

Extend the grammar of concept-name to allow for splicing reflections of concepts:

concept-name : identifier + splice-template-name

Modify paragraph 2 to account for splicing reflections of concepts:

A concept-definition declares a concept. Its concept-name shall be an identifier , and the identifier becomes a concept-name referring to that concept within its scope. The optional attribute-specifier-seq appertains to the concept.

Add to the list of never-type-dependent expression forms in 13.8.3.3 [temp.dep.expr] /4:

literal sizeof unary-expression sizeof ( type-id ) sizeof ... ( identifier ) alignof ( type-id ) typeid ( expression ) typeid ( type-id ) :: opt delete cast-expression :: opt delete [ ] cast-expression throw assignment-expression opt noexcept ( expression ) requires-expression + reflect-expression

Add a new paragraph at the end of 13.8.3.3 [temp.dep.expr] :

9 A primary-expression of the form splice-specifier or template splice-specifier < template-argument-list opt > is type-dependent if the splice-specifier is value-dependent or if the optional template-argument-list contains a value-dependent non-type or template argument, or a dependent type argument.

Add at the end of 13.8.3.4 [temp.dep.constexpr] /2 (before the note):

2 An id-expression is value-dependent if: (2.1) […] Expressions of the following form are value-dependent if the unary-expression or expression is type-dependent or the type-id is dependent: sizeof unary-expression sizeof ( type-id ) typeid ( expression ) typeid ( type-id ) alignof ( type-id ) noexcept ( expression ) A reflect-expression is value-dependent if the operand of the reflection operator is a type-dependent or value-dependent expression or if that operand is a dependent type-id , a dependent namespace-name , or a dependent template-name .

Add a new paragraph after 13.8.3.4 [temp.dep.constexpr] /4:

6 A primary-expression of the form splice-specifier or template splice-specifier < template-argument-list opt > is value-dependent if the constant-expression is value-dependent or if the optional template-argument-list contains a value-dependent non-type or template argument, or a dependent type argument.

5.2 Library

For convenience, we’re going to add a new library element to 16.3.2.4 [structure.specifications] /3:

3 Descriptions of function semantics contain the following elements (as appropriate): (3.1) Constraints : […] (3.2) Mandates : the conditions that, if not met, render the program ill-formed. […] (3.2+1) Constant When : the conditions that are required for a call to this function to be a core constant expression ([expr.const])

Insert before paragraph 7:

6 Let F denote a standard library function ([global.functions]), a standard library static member function, or an instantiation of a standard library function template. Unless F is designated an addressable function , the behavior of a C++ program is unspecified (possibly ill-formed) if it explicitly or implicitly attempts to form a pointer to F. […] 7pre Let F denote a standard library function, member function, or function template. If F does not designate an addressable function, it is unspecified if or how a reflection value designating the associated entity can be formed. [  Note 1: E.g., std :: meta :: members_of might not produce reflections of standard functions that an implementation handles through an extra-linguistic mechanism. —  end note  ] 7 A translation unit shall not declare namespace std to be an inline namespace ([namespace.def]).

Add a new primary type category type trait:

Header < type_traits > synopsis … // [meta.unary.cat], primary type categories template<class T> struct is_void; ... template<class T> struct is_function; + template<class T> struct is_reflection; // [meta.unary.cat], primary type categories template<class T> constexpr bool is_void_v = is_void<T>::value; ... template<class T> constexpr bool is_function_v = is_function<T>::value; + template<class T> + constexpr bool is_reflection_v = is_reflection<T>::value;

Add the is_reflection primary type category to the table in paragraph 3:

Template Condition Comments
template <class T> struct is_void; is
template <class T> struct is_reflection; is meta::info

Add a new subsection in 21 [meta] after 21.3 [type.traits] :

Header < meta > synopsis #include <initializer_list> #include <ranges> #include <string_view> #include <vector> namespace std::meta { using info = decltype(^::); // [meta.reflection.operators], operator representations enum class operators { see below ; }; using enum operators; consteval auto operator_of(info r) -> operators; // [meta.reflection.names], reflection names and locations consteval string_view identifier_of(info r); consteval string_view u8identifier_of(info r); consteval bool has_identifier(info r); consteval string_view display_string_of(info r); consteval string_view u8display_string_of(info r); consteval source_location source_location_of(info r); // [meta.reflection.queries], reflection queries consteval bool is_public(info r); consteval bool is_protected(info r); consteval bool is_private(info r); consteval bool is_virtual(info r); consteval bool is_pure_virtual(info r); consteval bool is_override(info r); consteval bool is_final(info r); consteval bool is_deleted(info r); consteval bool is_defaulted(info r); consteval bool is_user_provided(info r); consteval bool is_explicit(info r); consteval bool is_noexcept(info r); consteval bool is_bit_field(info r); consteval bool is_enumerator(info r); consteval bool is_const(info r); consteval bool is_volatile(info r); consteval bool is_lvalue_reference_qualified(info r); consteval bool is_rvalue_reference_qualified(info r); consteval bool has_static_storage_duration(info r); consteval bool has_thread_storage_duration(info r); consteval bool has_automatic_storage_duration(info r); consteval bool has_internal_linkage(info r); consteval bool has_module_linkage(info r); consteval bool has_external_linkage(info r); consteval bool has_linkage(info r); consteval bool is_complete_type(info r); consteval bool is_namespace(info r); consteval bool is_variable(info r); consteval bool is_type(info r); consteval bool is_type_alias(info r); consteval bool is_namespace_alias(info r); consteval bool is_function(info r); consteval bool is_conversion_function(info r); consteval bool is_operator_function(info r); consteval bool is_literal_operator(info r); consteval bool is_special_member(info r); consteval bool is_constructor(info r); consteval bool is_default_constructor(info r); consteval bool is_copy_constructor(info r); consteval bool is_move_constructor(info r); consteval bool is_assignment(info r); consteval bool is_copy_assignment(info r); consteval bool is_move_assignment(info r); consteval bool is_destructor(info r); consteval bool is_template(info r); consteval bool is_function_template(info r); consteval bool is_variable_template(info r); consteval bool is_class_template(info r); consteval bool is_alias_template(info r); consteval bool is_conversion_function_template(info r); consteval bool is_operator_function_template(info r); consteval bool is_literal_operator_template(info r); consteval bool is_constructor_template(info r); consteval bool is_concept(info r); consteval bool has_template_arguments(info r); consteval bool is_value(info r); consteval bool is_object(info r); consteval bool is_structured_binding(info r); consteval bool is_class_member(info entity); consteval bool is_namespace_member(info entity); consteval bool is_nonstatic_data_member(info r); consteval bool is_static_member(info r); consteval bool is_base(info r); consteval bool has_default_member_initializer(info r); consteval info type_of(info r); consteval info object_of(info r); consteval info value_of(info r); consteval info parent_of(info r); consteval info dealias(info r); consteval info template_of(info r); consteval vector<info> template_arguments_of(info r); // [meta.reflection.member.queries], reflection member queries consteval vector<info> members_of(info type); consteval vector<info> bases_of(info type); consteval vector<info> static_data_members_of(info type); consteval vector<info> nonstatic_data_members_of(info type); consteval vector<info> subobjects_of(info type); consteval vector<info> enumerators_of(info type_enum); // [meta.reflection.member.access], reflection member access queries struct access_context { // access context construction static consteval access_context current() noexcept; consteval access_context() noexcept; private: info context_ ; // exposition-only }; consteval bool is_accessible( info r, access_context from = access_context::current()); consteval vector<info> accessible_members_of( info target, access_context from = access_context::current()); consteval vector<info> accessible_bases_of( info target, access_context from = access_context::current()); consteval vector<info> accessible_nonstatic_data_members_of( info target, access_context from = access_context::current()); consteval vector<info> accessible_static_data_members_of( info target, access_context from = access_context::current()); consteval vector<info> accessible_subobjects_of( info target, access_context from = access_context::current()); // [meta.reflection.layout], reflection layout queries struct member_offsets { size_t bytes; size_t bits; constexpr size_t total_bits() const; auto operator<=>(member_offsets const&) const = default; }; consteval member_offsets offset_of(info entity); consteval size_t size_of(info entity); consteval size_t alignment_of(info entity); consteval size_t bit_size_of(info entity); // [meta.reflection.extract], value extraction template<class T> consteval T extract(info); // [meta.reflection.substitute], reflection substitution template <class R> concept reflection_range = see below ; template <reflection_range R = initializer_list<info>> consteval bool can_substitute(info templ, R&& arguments); template <reflection_range R = initializer_list<info>> consteval info substitute(info templ, R&& arguments); // [meta.reflection.result], expression result reflection template<class T> consteval info reflect_value(T value); template<class T> consteval info reflect_object(T& object); template<class T> consteval info reflect_function(T& fn); template <reflection_range R = initializer_list<info>> consteval info reflect_invoke(info target, R&& args); template <reflection_range R1 = initializer_list<info>, reflection_range R2 = initializer_list<info>> consteval info reflect_invoke(info target, R1&& tmpl_args, R2&& args); // [meta.reflection.define_class], class definition generation struct data_member_options_t { struct name_type { template<class T> requires constructible_from<u8string, T> consteval name_type(T &&); template<class T> requires constructible_from<string, T> consteval name_type(T &&); }; optional<name_type> name; optional<int> alignment; optional<int> width; bool no_unique_address = false; }; consteval info data_member_spec(info type, data_member_options_t options = {}); consteval bool is_data_member_spec(info r); template <reflection_range R = initializer_list<info>> consteval info define_class(info type_class, R&&); // [meta.reflection.static_string], static string generation consteval const char* define_static_string(string_view str); consteval const char8_t* define_static_string(u8string_view str); // [meta.reflection.unary.cat], primary type categories consteval bool type_is_void(info type); consteval bool type_is_null_pointer(info type); consteval bool type_is_integral(info type); consteval bool type_is_floating_point(info type); consteval bool type_is_array(info type); consteval bool type_is_pointer(info type); consteval bool type_is_lvalue_reference(info type); consteval bool type_is_rvalue_reference(info type); consteval bool type_is_member_object_pointer(info type); consteval bool type_is_member_function_pointer(info type); consteval bool type_is_enum(info type); consteval bool type_is_union(info type); consteval bool type_is_class(info type); consteval bool type_is_function(info type); consteval bool type_is_reflection(info type); // [meta.reflection.unary.comp], composite type categories consteval bool type_is_reference(info type); consteval bool type_is_arithmetic(info type); consteval bool type_is_fundamental(info type); consteval bool type_is_object(info type); consteval bool type_is_scalar(info type); consteval bool type_is_compound(info type); consteval bool type_is_member_pointer(info type); // [meta.reflection unary.prop], type properties consteval bool type_is_const(info type); consteval bool type_is_volatile(info type); consteval bool type_is_trivial(info type); consteval bool type_is_trivially_copyable(info type); consteval bool type_is_standard_layout(info type); consteval bool type_is_empty(info type); consteval bool type_is_polymorphic(info type); consteval bool type_is_abstract(info type); consteval bool type_is_final(info type); consteval bool type_is_aggregate(info type); consteval bool type_is_signed(info type); consteval bool type_is_unsigned(info type); consteval bool type_is_bounded_array(info type); consteval bool type_is_unbounded_array(info type); consteval bool type_is_scoped_enum(info type); template <reflection_range R = initializer_list<info>> consteval bool type_is_constructible(info type, R&& type_args); consteval bool type_is_default_constructible(info type); consteval bool type_is_copy_constructible(info type); consteval bool type_is_move_constructible(info type); consteval bool type_is_assignable(info type_dst, info type_src); consteval bool type_is_copy_assignable(info type); consteval bool type_is_move_assignable(info type); consteval bool type_is_swappable_with(info type_dst, info type_src); consteval bool type_is_swappable(info type); consteval bool type_is_destructible(info type); template <reflection_range R = initializer_list<info>> consteval bool type_is_trivially_constructible(info type, R&& type_args); consteval bool type_is_trivially_default_constructible(info type); consteval bool type_is_trivially_copy_constructible(info type); consteval bool type_is_trivially_move_constructible(info type); consteval bool type_is_trivially_assignable(info type_dst, info type_src); consteval bool type_is_trivially_copy_assignable(info type); consteval bool type_is_trivially_move_assignable(info type); consteval bool type_is_trivially_destructible(info type); template <reflection_range R = initializer_list<info>> consteval bool type_is_nothrow_constructible(info type, R&& type_args); consteval bool type_is_nothrow_default_constructible(info type); consteval bool type_is_nothrow_copy_constructible(info type); consteval bool type_is_nothrow_move_constructible(info type); consteval bool type_is_nothrow_assignable(info type_dst, info type_src); consteval bool type_is_nothrow_copy_assignable(info type); consteval bool type_is_nothrow_move_assignable(info type); consteval bool type_is_nothrow_swappable_with(info type_dst, info type_src); consteval bool type_is_nothrow_swappable(info type); consteval bool type_is_nothrow_destructible(info type); consteval bool type_is_implicit_lifetime(info type); consteval bool type_has_virtual_destructor(info type); consteval bool type_has_unique_object_representations(info type); consteval bool type_reference_constructs_from_temporary(info type_dst, info type_src); consteval bool type_reference_converts_from_temporary(info type_dst, info type_src); // [meta.reflection.unary.prop.query], type property queries consteval size_t type_alignment_of(info type); consteval size_t type_rank(info type); consteval size_t type_extent(info type, unsigned i = 0); // [meta.reflection.rel], type relations consteval bool type_is_same(info type1, info type2); consteval bool type_is_base_of(info type_base, info type_derived); consteval bool type_is_convertible(info type_src, info type_dst); consteval bool type_is_nothrow_convertible(info type_src, info type_dst); consteval bool type_is_layout_compatible(info type1, info type2); consteval bool type_is_pointer_interconvertible_base_of(info type_base, info type_derived); template <reflection_range R = initializer_list<info>> consteval bool type_is_invocable(info type, R&& type_args); template <reflection_range R = initializer_list<info>> consteval bool type_is_invocable_r(info type_result, info type, R&& type_args); template <reflection_range R = initializer_list<info>> consteval bool type_is_nothrow_invocable(info type, R&& type_args); template <reflection_range R = initializer_list<info>> consteval bool type_is_nothrow_invocable_r(info type_result, info type, R&& type_args); // [meta.reflection.trans.cv], const-volatile modifications consteval info type_remove_const(info type); consteval info type_remove_volatile(info type); consteval info type_remove_cv(info type); consteval info type_add_const(info type); consteval info type_add_volatile(info type); consteval info type_add_cv(info type); // [meta.reflection.trans.ref], reference modifications consteval info type_remove_reference(info type); consteval info type_add_lvalue_reference(info type); consteval info type_add_rvalue_reference(info type); // [meta.reflection.trans.sign], sign modifications consteval info type_make_signed(info type); consteval info type_make_unsigned(info type); // [meta.reflection.trans.arr], array modifications consteval info type_remove_extent(info type); consteval info type_remove_all_extents(info type); // [meta.reflection.trans.ptr], pointer modifications consteval info type_remove_pointer(info type); consteval info type_add_pointer(info type); // [meta.reflection.trans.other], other transformations consteval info type_remove_cvref(info type); consteval info type_decay(info type); template <reflection_range R = initializer_list<info>> consteval info type_common_type(R&& type_args); template <reflection_range R = initializer_list<info>> consteval info type_common_reference(R&& type_args); consteval info type_underlying_type(info type); template <reflection_range R = initializer_list<info>> `consteval info type_invoke_result(info type, R&& type_args); consteval info type_unwrap_reference(info type); consteval info type_unwrap_ref_decay(info type); }
enum class operators { see below ; } ; 1 This enum class specifies constants used to identify operators that can be overloaded, with the meanings listed in Table 1. The values of the constants are distinct. Table 1: Enum class operators [meta.reflection.operators] Constant Correspoding operator op_new operator new op_delete operator delete op_array_new operator new [] op_array_delete operator delete [] op_co_await operator coawait op_parentheses operator () op_square_brackets operator [] op_arrow operator -> op_arrow_asterisk operator ->* op_tilde operator ~ op_exclamation_mark operator ! op_plus operator + op_minus operator - op_asterisk operator * op_solidus operator / op_percent operator % op_caret operator ^ op_ampersand operator & op_pipe operator | op_equals operator = op_plus_equals operator += op_minus_equals operator -= op_asterisk_equals operator *= op_solidus_equals operator /= op_percent_equals operator %= op_caret_equals operator ^= op_ampersand_equals operator &= op_pipe_equals operator |= op_equals_equals operator == op_exclamation_equals operator != op_less operator < op_greater operator > op_less_equals operator <= op_greater_equals operator >= op_three_way_compare operator <=> op_ampersand_ampersand operator && op_pipe_pipe operator || op_less_less operator << op_greater_greater operator >> op_less_less_equals operator <<= op_greater_greater_equals operator >>= op_plus_plus operator ++ op_minus_minus operator -- op_comma operator , consteval operators operator_of ( info r ) ; 2 Constant When : r represents an operator function or operator function template. 3 Returns : The value of the enumerator from operators for which the corresponding operator has the same unqualified name as the entity represented by r .
consteval string_view identifier_of ( info r ) ; consteval u8string_view u8identifier_of ( info r ) ; 1 Let E be UTF-8 if returning a u8string_view , and otherwise the ordinary string literal encoding. 2 Constant When : If r represents a function, then when the function is not a constructor, destructor, operator function, or conversion function. Otherwise, if r is a function template, then when the function template is not a constructor template, a conversion function template, or an operator function template. Otherwise, if r represents a variable, an entity that is not a function or function template, or an alias of a type or namespace, then when the declaration of what is represented by r introduces an identifier representable by E . Otherwise, if r represents a base class specifier for which the base class is a named type, then when the name of that type is an identifier representable by E . Otherwise, when r represents a description of the declaration of a non-static data member, and the declaration of any data member having the properties represented by r would introduce an identifier representable by E . 3 Returns : (3.1) If r represents a literal operator or literal operator template, then the ud-suffix of the operator or operator template. (3.2) Otherwise, if r represents a variable, entity, or alias of a type or namespace, then the identifier introduced by the the declaration of what is represented by r . (3.3) Otherwise, if r represents a base class specifier, then the identifier introduced by the declaration of the type of the base class. (3.4) Otherwise (if r represents a description of the declaration of a non-static data member), then the identifier that would be introduced by the declaration of a data member having the properties represented by r . consteval string_view display_string_of ( info r ) ; consteval u8string_view u8display_string_of ( info r ) ; 4 Constant When : If returning string_view , the implementation-defined name is representable using the ordinary string literal encoding. 5 Returns : An implementation-defined string_view or u8string_view , respectively, suitable for identifying the reflected construct. consteval bool has_identifier ( info r ) ; 6 Returns : If r represents a variable, entity, or alias of a type or namespace, then true if the declaration of what is represented by r introduces an identifier. Otherwise, if r represents a base class specifier for which the base class is a named type, then true . Otherwise if r represents a description of the declaration of a non-static data member, then true if the declaration of a data member having the properties represented by r would introduce an identifier. Otherwise, false . consteval source_location source_location_of ( info r ) ; 7 Returns : An implementation-defined source_location corresponding to the reflected construct.
consteval bool is_public ( info r ) ; consteval bool is_protected ( info r ) ; consteval bool is_private ( info r ) ; 1 Returns : true if r represents a class member or base class specifier that is public, protected, or private, respectively. Otherwise, false . consteval bool is_virtual ( info r ) ; 2 Returns : true if r represents either a virtual member function or a virtual base class specifier. Otherwise, false . consteval bool is_pure_virtual ( info r ) ; consteval bool is_override ( info r ) ; 3 Returns : true if r represents a member function that is pure virtual or overrides another member function, respectively. Otherwise, false . consteval bool is_final ( info r ) ; 4 Returns : true if r represents a final class or a final member function. Otherwise, false . consteval bool is_deleted ( info r ) ; consteval bool is_defaulted ( info r ) ; 5 Returns : true if r represents a function that is defined as deleted ([dcl.fct.def.delete])or defined as defaulted ([dcl.fct.def.default]), respectively. Otherwise, false . consteval bool is_user_provided ( info r ) ; 6 Constant When : r represents a function. 7 Returns : true if r represents a user-provided ( 9.5.2 [dcl.fct.def.default] ) function. Otherwise, false . consteval bool is_explicit ( info r ) ; 8 Returns : true if r represents a member function that is declared explicit. Otherwise, false . [  Note 1: If r represents a member function template that is declared explicit , is_explicit ( r ) is still false because in general such queries for templates cannot be answered. —  end note  ] consteval bool is_noexcept ( info r ) ; 9 Returns : true if r represents a noexcept function type or a function or member function that is declared noexcept . Otherwise, false . [  Note 2: If r represents a function template that is declared noexcept , is_noexcept ( r ) is still false because in general such queries for templates cannot be answered. —  end note  ] consteval bool is_bit_field ( info r ) ; 10 Returns : true if r represents a bit-field, or if r represents a description of the declaration of a non-static data member for which any data member declared with the properties represented by r would be a bit-field. Otherwise, false . consteval bool is_enumerator ( info r ) ; 11 Returns : true if r represents an enumerator. Otherwise, false . consteval bool is_const ( info r ) ; consteval bool is_volatile ( info r ) ; 12 Returns : true if r represents a const or volatile type (respectively), a const- or volatile-qualified function type (respectively), or an object, variable, non-static data member, or function with such a type. Otherwise, false . consteval bool is_lvalue_reference_qualified ( info r ) ; consteval bool is_rvalue_reference_qualified ( info r ) ; 13 Returns : true if r represents a lvalue- or rvalue-reference qualified function type (respectively), or a member function with such a type. Otherwise, false . consteval bool has_static_storage_duration ( info r ) ; consteval bool has_thread_storage_duration ( info r ) ; consteval bool has_automatic_storage_duration ( info r ) ; 14 Returns : true if r represents an object or variable that has static, thread, or automatic storage duration, respectively ([basic.stc]). Otherwise, false . consteval bool has_internal_linkage ( info r ) ; consteval bool has_module_linkage ( info r ) ; consteval bool has_external_linkage ( info r ) ; consteval bool has_linkage ( info r ) ; 15 Returns : true if r represents a variable, function, type, template, or namespace whose name has internal linkage, module linkage, external linkage, or any linkage, respectively ([basic.link]). Otherwise, false . consteval bool is_complete_type ( info r ) ; 16 Effects : If is_type ( r ) is true and dealias ( r ) represents a class template specialization with a reachable definition, the specialization is instantiated. 17 Returns : true if is_type ( r ) is true and the type represented by dealias ( r ) is not an incomplete type ([basic.types]). Otherwise, false . consteval bool is_namespace ( info r ) ; 18 Returns : true if r represents a namespace or namespace alias. Otherwise, false . consteval bool is_variable ( info r ) ; 19 Returns : true if r represents a variable. Otherwise, false . consteval bool is_type ( info r ) ; 20 Returns : true if r represents a type or a typedef-name . Otherwise, false . consteval bool is_type_alias ( info r ) ; consteval bool is_namespace_alias ( info r ) ; 21 Returns : true if r represents a typedef-name or namespace alias, respectively [  Note 3: An instantiation of an alias template is a typedef-name —  end note  ] . Otherwise, false . consteval bool is_function ( info r ) ; 22 Returns : true if r represents a function. Otherwise, false . consteval bool is_conversion_function ( info r ) ; consteval bool is_operator_function ( info r ) ; consteval bool is_literal_operator ( info r ) ; 23 Returns : true if r represents a conversion function, operator function, or literal operator, respectively. Otherwise, false . consteval bool is_special_member ( info r ) ; consteval bool is_constructor ( info r ) ; consteval bool is_default_constructor ( info r ) ; consteval bool is_copy_constructor ( info r ) ; consteval bool is_move_constructor ( info r ) ; consteval bool is_assignment ( info r ) ; consteval bool is_copy_assignment ( info r ) ; consteval bool is_move_assignment ( info r ) ; consteval bool is_destructor ( info r ) ; 24 Returns : true if r represents a special member function, non-template constructor, default constructor, copy constructor, move constructor, assignment operator, copy assignment operator, move assignment operator, or destructor, respectively. Otherwise, false . consteval bool is_template ( info r ) ; 25 Returns : true if r represents a function template, class template, variable template, alias template, or concept. Otherwise, false . 26 [  Note 4: A template specialization is not a template. is_template (^ std :: vector ) is true but is_template (^ std :: vector < int >) is false . —  end note  ] consteval bool is_function_template ( info r ) ; consteval bool is_variable_template ( info r ) ; consteval bool is_class_template ( info r ) ; consteval bool is_alias_template ( info r ) ; consteval bool is_conversion_function_template ( info r ) ; consteval bool is_operator_function_template ( info r ) ; consteval bool is_literal_operator_template ( info r ) ; consteval bool is_constructor_template ( info r ) ; consteval bool is_concept ( info r ) ; 27 Returns : true if r represents a function template, variable template, class template, alias template, conversion function template, operator function template, literal operator template, constructor template, or concept respectively. Otherwise, false . consteval bool has_template_arguments ( info r ) ; 28 Returns : true if r represents a specialization of a function template, variable template, class template, or an alias template. Otherwise, false . consteval bool is_value ( info r ) ; consteval bool is_object ( info r ) ; 29 Returns : true if r represents a value or object, respectively. Otherwise, false . consteval bool is_structured_binding ( info r ) ; 30 Returns : true if r represents a structured binding. Otherwise, false . consteval bool is_class_member ( info r ) ; consteval bool is_namespace_member ( info r ) ; consteval bool is_nonstatic_data_member ( info r ) ; consteval bool is_static_member ( info r ) ; consteval bool is_base ( info r ) ; 31 Returns : true if r represents a class member, namespace member, non-static data member, static member, base class specifier, respectively. Otherwise, false . consteval bool has_default_member_initializer ( info r ) ; 32 Returns : true if r represents a non-static data member that has a default member initializer. Otherwise, false . consteval info type_of ( info r ) ; 33 Constant When : r represents a value, object, variable, function that is not a constructor or destructor, enumerator, non-static data member, bit-field, base class specifier, or description of the declaration of a non-static data member. 34 Returns : If r represents an entity or variable for which every declaration specifies its type using the same typedef-name , a base class specifier whose base class is specified using a typedef-name , or a description of the declaration of a non-static data member whose type is specified using a typedef-name , then a reflection of the typedef-name . Otherwise, if r represents an entity or variable, then the type of what is represented by r . Otherwise, if r represents a base class specifier, then the type of the base class. Otherwise, the type of any data member declared with the properties represented by r . consteval info object_of ( info r ) ; 35 Constant When : r is a reflection representing either an object or a variable denoting an object with static storage duration ([expr.const]). 36 Returns : If r is a reflection of a variable, then a reflection of the object denoted by the variable. Otherwise, r . [  Example 1: int x; int & y = x; static_assert (^ x != ^ y ) ; // OK, x and y are different variables so their // reflections compare different static_assert ( object_of (^ x ) == object_of (^ y )) ; // OK, because y is a reference // to x, their underlying objects are the same —  end example  ] consteval info value_of ( info r ) ; 37 Constant When : r is a reflection representing either an object or variable usable in constant expressions ([expr.const]) whose type is a structural type ([temp.type]), an enumerator, or a value. 38 Returns : If r is a reflection of an object o , or a reflection of a variable which designates an object o , then a reflection of the value held by o . The reflected value has type dealias ( type_of ( o )) , with the cv-qualifiers removed if this is a scalar type. Otherwise, if r is a reflection of an enumerator, then a reflection of the value of the enumerator. Otherwise, r . [  Example 2: constexpr int x = 0 ; constexpr int y = 0 ; static_assert (^ x != ^ y ) ; // OK, x and y are different variables so their // reflections compare different static_assert ( value_of (^ x ) == value_of (^ y )) ; // OK, both value_of(^x) and value_of(^y) represent // the value 0 static_assert ( value_of (^ x ) == reflect_value ( 0 )) ; // OK, likewise —  end example  ] consteval info parent_of ( info r ) ; 39 Constant When : r represents a variable, structured binding, function, enumerator, class, class member, bit-field, template, namespace, alias of a type or namespace, or base class specifier. 40 Returns : A reflection of that entity’s immediately enclosing class, function, or namespace. consteval info dealias ( info r ) ; 41 Returns : If r represents a typedef-name or namespace alias A , then a reflection representing the entity named by A . Otherwise, r . 42 [  Example 3: using X = int; using Y = X; static_assert(dealias(^int) == ^int); static_assert(dealias(^X) == ^int); static_assert(dealias(^Y) == ^int); —  end example  ] consteval info template_of ( info r ) ; consteval vector < info > template_arguments_of ( info r ) ; 43 Constant When : has_template_arguments ( r ) is true . 44 Returns : A reflection of the template of r , and the reflections of the template arguments of the specialization represented by r , respectively. 45 [  Example 4: template <class T, class U=T> struct Pair { }; template <class T> using PairPtr = Pair<T*>; static_assert(template_of(^Pair<int>) == ^Pair); static_assert(template_arguments_of(^Pair<int>).size() == 2); static_assert(template_of(^PairPtr<int>) == ^PairPtr); static_assert(template_arguments_of(^PairPtr<int>).size() == 1); —  end example  ]
consteval vector < info > members_of ( info r ) ; 1 Constant When : r is a reflection representing either a complete class type or a namespace. 2 A member of a class or namespace is a representable member if it is either a class, a typedef-name , a primary class template, function template, primary variable template, alias template, or concept, a variable or reference, a function, a non-static data member, a namespace, or a namespace alias. [  Note 1: Counterexamples of representable members include: injected class names, partial template specializations, friend declarations, and static assertions. —  end note  ] 3 Effects : If dealias ( r ) represents a class template specialization with a reachable definition, the specialization is instantiated. 4 Returns : A vector containing all representable members whose first declaration is directly within a definition of the entity E represented by r . If E represents a class C , then the vector also contains reflections representing all unnamed bit-fields declared within the member-specification of C . Non-static data members are indexed in the order in which they are declared, but the order of other kinds of members is unspecified. [  Note 2: Base classes are not members. —  end note  ] consteval vector < info > bases_of ( info type ) ; 5 Constant When : type is a reflection representing a complete class type. 6 Effects : If dealias ( type ) represents a class template specialization with a reachable definition, the specialization is instantiated. 7 Returns : Let C be the type represented by type . A vector containing the reflections of all the direct base class specifiers b , if any, of C . The base class specifiers are indexed in the order in which they appear in the base-specifier-list of C . consteval vector < info > static_data_members_of ( info type ) ; 8 Constant When : type represents a complete class type. 9 Effects : If dealias ( type ) represents a class template specialization with a reachable definition, the specialization is instantiated. 10 Returns : A vector containing the reflections of the static data members of the type represented by type . consteval vector < info > nonstatic_data_members_of ( info type ) ; 11 Constant When : type represents a complete class type. 12 Effects : If dealias ( type ) represents a class template specialization with a reachable definition, the specialization is instantiated. 13 Returns : A vector containing the reflections of the non-static data members of the type represented by type , in the order in which they are declared. consteval vector < info > subobjects_of ( info type ) ; 14 Constant When : type is a reflection representing a complete class type. 15 Effects : If dealias ( type ) represents a class template specialization with a reachable definition, the specialization is instantiated. 16 Returns : A vector containing all the reflections in bases_of ( type ) followed by all the reflections in nonstatic_data_members_of ( type ) . consteval vector < info > enumerators_of ( info type_enum ) ; 17 Constant When : type_enum is a reflection representing a complete enumeration type. 18 Returns : A vector containing the reflections of each enumerator of the enumeration represented by type_enum , in the order in which they are declared.
struct access_context { // access context construction static consteval access_context current () noexcept ; consteval access_context () noexcept ; private : info context_ ; // exposition-only } ; 1 The type access_context is suitable for ensuring that only accessible members are reflected on. consteval access_context access_context :: current () noexcept ; 2 Effects : Initializes context_ to a reflection of the function, class, or namespace scope most nearly enclosing the function call. consteval access_context :: access_context () noexcept ; 3 Effects : Initializes context_ to ^:: . consteval bool is_accessible ( info target, access_context from = access_context :: current ()) ; 4 Constant When : target is a reflection representing a member or base class specifier of a class. 5 Let C be the class for which target represents a member or base class specifier. 6 Returns : (6.1) If target represents a class member, then true if the member is accessible at all program points within the definition of the entity represented by from . context_ when named in class C ([class.access]). (6.2) Otherwise, true if the base class represented by target is accessible at all program points within the definition of the entity represented by from . context_ . (6.3) Otherwise, false . consteval vector < info > accessible_members_of ( info target, access_context from = access_context :: current ()) ; 7 Constant When : target is a reflection representing a complete class type. from represents a function, class, or namespace. 8 Effects : If dealias ( type ) represents a class template specialization with a reachable definition, the specialization is instantiated. 9 Returns : A vector containing each element, e , of members_of ( target ) such that is_accessible ( e, from ) is true , in order. consteval vector < info > accessible_bases_of ( info target, access_context from = access_context :: current ()) ; 10 Constant When : target is a reflection representing a complete class type. from represents a function, class, or namespace. 11 Effects : If dealias ( type ) represents a class template specialization with a reachable definition, the specialization is instantiated. 12 Returns : A vector containing each element, e , of bases_of ( target ) such that is_accessible ( e, from ) is true , in order. consteval vector < info > accessible_nonstatic_data_members_of ( info target, access_context from = access_context :: current ()) ; 13 Constant When : target is a reflection representing a complete class type. from represents a function, class, or namespace. 14 Effects : If dealias ( type ) represents a class template specialization with a reachable definition, the specialization is instantiated. 15 Returns : A vector containing each element, e , of nonstatic_data_members_of ( target ) such that is_accessible ( e, from ) is true , in order. consteval vector < info > accessible_static_data_members_of ( info target, access_context from = access_context :: current ()) ; 16 Constant When : target is a reflection representing a complete class type. from represents a function, class, or namespace. 17 Effects : If dealias ( type ) represents a class template specialization with a reachable definition, the specialization is instantiated. 18 Returns : A vector containing each element, e , of static_data_members_of ( target ) such that is_accessible ( e, from ) is true , in order. consteval vector < info > accessible_subobjects_of ( info target, access_context from = access_context :: current ()) ; 19 Returns : A vector containing all the reflections in accessible_bases_of ( target, from ) followed by all the reflections in accessible_nonstatic_data_members_of ( target, from ) .
constexpr size_t member_offsets :: total_bits () const ; 1 Returns : bytes * CHAR_BIT + bits . consteval member_offsets offset_of ( info r ) ; 2 Constant When : r is a reflection representing a non-static data member or non-virtual base class specifier. 3 Let V be the offset in bits from the beginning of an object of type parent_of ( r ) to the subobject associated with the entity represented by r . 4 Returns : { V / CHAR_BIT * CHAR_BIT, V % CHAR_BIT } . consteval size_t size_of ( info r ) ; 5 Constant When : r is a reflection of a type, non-static data member, base class specifier, object, value, variable, or description of the declaration of a non-static data member. 6 Returns If r represents a type T , then sizeof ( T ) . Otherwise, size_of ( type_of ( r )) . consteval size_t alignment_of ( info r ) ; 7 Constant When : r is a reflection representing an object, variable, type, non-static data member that is not a bit-field, base class specifier, or description of the declaration of a non-static data member. 8 Returns : If r represents a type, object, or variable, then the alignment requirement of the entity. Otherwise, if r represents a base class specifier, then alignment_of ( type_of ( r )) . Otherwise, if r represents a non-static data member, then the alignment requirement of the subobject associated with the reflected non-static data member within any object of type parent_of ( r ) . Otherwise, if r represents a description of the declaration of a non-static data member, then the alignment-specifier of any data member declared having the properties described by r . consteval size_t bit_size_of ( info r ) ; 9 Constant When : r is a reflection of an object, value, variable, type, non-static data member, base class specifier, or description of the declaration of a non-static data member. 10 Returns If r represents a type, then the size in bits of any object having the reflected type. Otherwise, if r represents a non-static data member that is a bit-field, then the width of the reflected bit-field. Otherwise, if r represents a description of the declaration of a non-static data member for which any data member declared having the properties described by r would be a bit-field, then the width of such a bit-field. Otherwise, bit_size_of ( type_of ( r )) .
template < class T > consteval T extract ( info r ) ; 1 Constant When : r is a reflection representing a value, object, variable, function, enumerator, or non-static data member that is not a bit-field. If r represents a value or enumerator, then T is not a reference type. If r represents a value or enumerator of type U , or if r represents a variable or object of non-reference type U , then the cv-unqualified types of T and U are the same. If r represents a variable, object, or function with type U , and T is a reference type, then the cv-unqualified types of T and U are the same, and T is either U or more cv-qualified than U . If r represents a non-static data member, or if r represents a function and T is a reference type, then the statement T v = & expr , where expr is an lvalue naming the entity represented by r , is well-formed. 2 Returns : If r represents a value or enumerator, then the entity represented by r . Otherwise, if r represents an object, variable, or enumerator and T is not a reference type, then the result of an lvalue-to-rvalue conversion applied to an expression naming the entity represented by r . Otherwise, if r represents an object, variable, or function and T is a reference type, then the result of an lvalue naming the entity represented by r . Otherwise, if r represents a function or non-static data member, then a pointer value designating the entity represented by r .
template < class R > concept reflection_range = ranges :: input_range < R > && same_as < ranges :: range_value_t < R > , info > && same_as < remove_cvref_t < ranges :: range_reference_t < R >> , info > ; template < reflection_range R = initializer_list < info >> consteval bool can_substitute ( info templ, R && arguments ) ; 1 Constant When : templ represents a template. 2 Let Z be the template represented by templ and let Args ... be the sequence of entities or aliases represented by the elements of arguments . 3 Returns : true if Z < Args ...> is a valid template-id ([temp.names]). Otherwise, false . 4 Remarks : If attempting to substitute leads to a failure outside of the immediate context, the program is ill-formed. template < reflection_range R = initializer_list < info >> consteval info substitute ( info templ, R && arguments ) ; 5 Constant When : can_substitute ( templ, arguments ) is true . 6 Let Z be the template represented by templ and let Args ... be the sequence of entities or aliases represented by the elements of arguments . 7 Returns : ^ Z < Args ...> .
template < typename T > consteval info reflect_value ( T expr ) ; 1 Constant When : T is a structural type that is not a reference type. Any subobject of the value computed by expr having reference or pointer type designates an entity that is a permitted result of a constant expression ([expr.const]). 2 Returns : A reflection of the value computed by an lvalue-to-rvalue conversion applied to expr . The type of the reflected value is the cv-unqualified version of T . template < typename T > consteval info reflect_object ( T & expr ) ; 3 Constant When : T is not a function type. expr designates an entity that is a permitted result of a constant expression. 4 Returns : A reflection of the object referenced by expr . template < typename T > consteval info reflect_function ( T & expr ) ; 5 Constant When : T is a function type. 6 Returns : ^ fn , where fn is the function referenced by expr . template < reflection_range R = initializer_list < info >> consteval info reflect_invoke ( info target, R && args ) ; template < reflection_range R1 = initializer_list < info > , reflection_range R2 = initializer_list < info >> consteval info reflect_invoke ( info target, R1 && tmpl_args, R2 && args ) ; 7 Let F be the entity represented by target , let Arg0 be the entity represented by the first element of args (if any), let Args ... be the sequence of entities represented by the elements of args excluding the first, and let TArgs ... be the sequence of entities or aliases represented by the elements of tmpl_args . 8 If F is a non-member function, a value of pointer to function type, a value of pointer to member type, or a value of closure type, then let INVOKE - EXPR be the expression INVOKE ( F, Arg0, Args ...) . Otherwise, if F is a member function, then let INVOKE - EXPR be the expression Arg0 . F ( Args ...) . Otherwise, if F is a constructor for a class C , then let INVOKE - EXPR be the expression C ( Arg0, Args ...) for which only the constructor F is considered by overload resolution. Otherwise, if F is a non-member function template or a member function template, then let INVOKE - EXPR be the expression F < TArgs ...>( Arg0, Args ...) or Arg0 . template F < TArgs ...>( Args ...) respectively. Otherwise, if F is a constructor template, then let INVOKE - EXPR be the expression C ( Arg0, Args ...) for which only the constructor F is considered by overload resolution, and TArgs ... are inferred as explicit template arguments for F . 9 Constant When : target represents a function, a constructor, a constructor template, a value, or a function template. If target represents a value of type T , then T is a pointer to function type, pointer to member type, or closure type. The expression INVOKE - EXPR is a well-formed constant expression of structural type. 10 Returns : A reflection of the result of the expression INVOKE - EXPR .
consteval info data_member_spec ( info type, data_member_options_t options = {}) ; 1 Constant When : type represents a type. If options . name contains a value, the string or u8string value that was used to initialize options . name , respectively interpreted using the ordinary string literal encoding or with UTF-8, contains a valid identifier ( 5.10 [lex.name] ). If options . width contains a value, then type represents an integral or (possibly cv-qualified) enumeration type, options . alignment contains no value, and options . no_unique_address is false. If options . alignment contains a value, it is an alignment value ([basic.align]) not less than the alignment requirement of the type represented by type . If options . width contains the value zero, options . name does not contain a value. 2 Returns : A reflection of a description of the declaration of a non-static data member with a type represented by type and optional characteristics designated by options . 3 Remarks : The reflection value being returned is only useful for consumption by define_class . No other function in std :: meta recognizes such a value. consteval bool is_data_member_spec ( info r ) ; 4 Returns : true if r is the description of the declaration of a non-static data member. Otherwise, false . template < reflection_range R = initializer_list < info >> consteval info define_class ( info class_type, R && mdescrs ) ; 5 Let d 1 , d 2 , …, d N denote the reflection values of the range mdescrs obtained by calling data_member_spec with type values t 1 , t 2 , … t N and option values o 1 , o 2 , … o N respectively. 6 Constant When : class_type represents an incomplete class type. mdescrs is a (possibly empty) range of reflection values obtained by calls to data_member_spec . [  Note 1: For example, class_type could be a specialization of a class template that has not been instantiated or explicitly specialized. —  end note  ] Each t i represents a type that is valid types for data members. 7 Effects : Defines class_type with properties as follows: (7.1) If class_type represents a specialization of a class template, the specialization is explicitly specialized. (7.2) Non-static data members are declared in the definition of class_type according to d 1 , d 2 , …, d N , in that order. (7.3) The type of the respective members are the types represented by the reflection values t 1 , t 2 , … t N . (7.4) If o K . no_unique_address (for some K ) is true , the corresponding member is declared with attribute [[ no_unique_address ]] . (7.5) If o K . width (for some K ) contains a value, the corresponding member is declared as a bit field with that value as its width. (7.6) If o K . alignment (for some K ) contains a value a , the corresponding member is declared with the alignment-specifier alignas ( a ) . (7.7) If o K . width (for some K ) contains the value zero, the corresponding member is declared without a name. Otherwise, if o K . name does not contain a value, the corresponding member is declared with an implementation-defined name. Otherwise, the corresponding member is declared with a name corresponding to the string or u8string value that was used to initialize o K . name . (7.8) If class_type is a union type and any of its members is not trivially default constructible, then it has a default constructor that is user-provided and has no effect. If class_type is a union type and any of its members is not trivially default destructible, then it has a default destructor that is user-provided and has no effect. 8 Returns : class_type .
consteval const char * define_static_string ( string_view str ) ; consteval const char8_t * define_static_string ( u8string_view str ) ; 1 Let S be a constexpr variable of array type with static storage duration, whose elements are of type const char or const char8_t respectively, for which there exists some k >= 0 such that: (1.1) S [ k + i ] == str [ i ] for all 0 <= i < str . size () , and (1.2) S [ k + str . size ()] == ' \0 ' . 2 Returns : & S [ k ] 3 Implementations are encouraged to return the same object whenever the same variant of these functions is called with the same argument.

[meta.reflection.unary] Unary type traits

1 Subclause [meta.reflection.unary] contains consteval functions that may be used to query the properties of a type at compile time. 2 For each function taking an argument of type meta :: info whose name contains type , a call to the function is a non-constant library call ( 3.34 [defns.nonconst.libcall] ) if that argument is not a reflection of a type or typedef-name . For each function taking an argument named type_args , a call to the function is a non-constant library call if any meta :: info in that range is not a reflection of a type or a typedef-name .
1 For any type or typedef-name T , for each function std :: meta :: type_ TRAIT defined in this clause, std :: meta :: type_ TRAIT (^ T ) equals the value of the corresponding unary type trait std :: TRAIT _v < T > as specified in 21.3.5.2 [meta.unary.cat] . consteval bool type_is_void ( info type ) ; consteval bool type_is_null_pointer ( info type ) ; consteval bool type_is_integral ( info type ) ; consteval bool type_is_floating_point ( info type ) ; consteval bool type_is_array ( info type ) ; consteval bool type_is_pointer ( info type ) ; consteval bool type_is_lvalue_reference ( info type ) ; consteval bool type_is_rvalue_reference ( info type ) ; consteval bool type_is_member_object_pointer ( info type ) ; consteval bool type_is_member_function_pointer ( info type ) ; consteval bool type_is_enum ( info type ) ; consteval bool type_is_union ( info type ) ; consteval bool type_is_class ( info type ) ; consteval bool type_is_function ( info type ) ; consteval bool type_is_reflection ( info type ) ; 2 [  Example 1: namespace std::meta { consteval bool type_is_void(info type) { // one example implementation return extract<bool>(substitute(^is_void_v, {type})); // another example implementation type = dealias(type); return type == ^void || type == ^const void || type == ^volatile void || type == ^const volatile void; } } —  end example  ]
1 For any type or typedef-name T , for each function std :: meta :: type_ TRAIT defined in this clause, std :: meta :: type_ TRAIT (^ T ) equals the value of the corresponding unary type trait std :: TRAIT _v < T > as specified in 21.3.5.3 [meta.unary.comp] . consteval bool type_is_reference ( info type ) ; consteval bool type_is_arithmetic ( info type ) ; consteval bool type_is_fundamental ( info type ) ; consteval bool type_is_object ( info type ) ; consteval bool type_is_scalar ( info type ) ; consteval bool type_is_compound ( info type ) ; consteval bool type_is_member_pointer ( info type ) ;
1 For any type or typedef-name T , for each function std :: meta :: type_ UNARY-TRAIT defined in this clause with signature bool ( std :: meta :: info ) , std :: meta :: type_ UNARY-TRAIT (^ T ) equals the value of the corresponding type property std :: UNARY-TRAIT _v < T > as specified in 21.3.5.4 [meta.unary.prop] . 2 For any types or typedef-names T and U , for each function std :: meta :: type_ BINARY-TRAIT defined in this clause with signature bool ( std :: meta :: info, std :: meta :: info ) , std :: meta :: type_ BINARY-TRAIT (^ T, ^ U ) equals the value of the corresponding type property std :: BINARY-TRAIT _v < T, U > as specified in 21.3.5.4 [meta.unary.prop] . 3 For any type or typedef-name T , pack of types or typedef-names U ... , and range r such that ranges :: to < vector >( r ) == vector {^ U ...} is true , for each function template std :: meta :: type_ VARIADIC-TRAIT defined in this clause, std :: meta :: type_ VARIADIC-TRAIT (^ T, r ) equals the value of the corresponding type property std :: VARIADIC-TRAIT _v < T, U ...> as specified in 21.3.5.4 [meta.unary.prop] . consteval bool type_is_const ( info type ) ; consteval bool type_is_volatile ( info type ) ; consteval bool type_is_trivial ( info type ) ; consteval bool type_is_trivially_copyable ( info type ) ; consteval bool type_is_standard_layout ( info type ) ; consteval bool type_is_empty ( info type ) ; consteval bool type_is_polymorphic ( info type ) ; consteval bool type_is_abstract ( info type ) ; consteval bool type_is_final ( info type ) ; consteval bool type_is_aggregate ( info type ) ; consteval bool type_is_signed ( info type ) ; consteval bool type_is_unsigned ( info type ) ; consteval bool type_is_bounded_array ( info type ) ; consteval bool type_is_unbounded_array ( info type ) ; consteval bool type_is_scoped_enum ( info type ) ; template < reflection_range R = initializer_list < info >> consteval bool type_is_constructible ( info type, R && type_args ) ; consteval bool type_is_default_constructible ( info type ) ; consteval bool type_is_copy_constructible ( info type ) ; consteval bool type_is_move_constructible ( info type ) ; consteval bool type_is_assignable ( info type_dst, info type_src ) ; consteval bool type_is_copy_assignable ( info type ) ; consteval bool type_is_move_assignable ( info type ) ; consteval bool type_is_swappable_with ( info type_dst, info type_src ) ; consteval bool type_is_swappable ( info type ) ; consteval bool type_is_destructible ( info type ) ; template < reflection_range R = initializer_list < info >> consteval bool type_is_trivially_constructible ( info type, R && type_args ) ; consteval bool type_is_trivially_default_constructible ( info type ) ; consteval bool type_is_trivially_copy_constructible ( info type ) ; consteval bool type_is_trivially_move_constructible ( info type ) ; consteval bool type_is_trivially_assignable ( info type_dst, info type_src ) ; consteval bool type_is_trivially_copy_assignable ( info type ) ; consteval bool type_is_trivially_move_assignable ( info type ) ; consteval bool type_is_trivially_destructible ( info type ) ; template < reflection_range R = initializer_list < info >> consteval bool type_is_nothrow_constructible ( info type, R && type_args ) ; consteval bool type_is_nothrow_default_constructible ( info type ) ; consteval bool type_is_nothrow_copy_constructible ( info type ) ; consteval bool type_is_nothrow_move_constructible ( info type ) ; consteval bool type_is_nothrow_assignable ( info type_dst, info type_src ) ; consteval bool type_is_nothrow_copy_assignable ( info type ) ; consteval bool type_is_nothrow_move_assignable ( info type ) ; consteval bool type_is_nothrow_swappable_with ( info type_dst, info type_src ) ; consteval bool type_is_nothrow_swappable ( info type ) ; consteval bool type_is_nothrow_destructible ( info type ) ; consteval bool type_is_implicit_lifetime ( info type ) ; consteval bool type_has_virtual_destructor ( info type ) ; consteval bool type_has_unique_object_representations ( info type ) ; consteval bool type_reference_constructs_from_temporary ( info type_dst, info type_src ) ; consteval bool type_reference_converts_from_temporary ( info type_dst, info type_src ) ;
1 For any type or typedef-name T , for each function std :: meta :: type_ PROP defined in this clause with signature size_t ( std :: meta :: info ) , std :: meta :: type_ PROP (^ T ) equals the value of the corresponding type property std :: PROP _v < T > as specified in 21.3.6 [meta.unary.prop.query] . 2 For any type or typedef-name T and unsigned integer value I , std :: meta :: type_extent (^ T, I ) equals std :: extent_v < T, I > ([meta.unary.prop.query]). consteval size_t type_alignment_of ( info type ) ; consteval size_t type_rank ( info type ) ; consteval size_t type_extent ( info type, unsigned i = 0 ) ;
1 The consteval functions specified in this clause may be used to query relationships between types at compile time. 2 For any types or typedef-name T and U , for each function std :: meta :: type_ REL defined in this clause with signature bool ( std :: meta :: info, std :: meta :: info ) , std :: meta :: type_ REL (^ T, ^ U ) equals the value of the corresponding type relation std :: REL _v < T, U > as specified in 21.3.7 [meta.rel] . 3 For any type or typedef-name T , pack of types or typedef-names U ... , and range r such that ranges :: to < vector >( r ) == vector {^ U ...} is true , for each binary function template std :: meta :: type_ VARIADIC-REL , std :: meta :: type_ VARIADIC-REL (^ T, r ) equals the value of the corresponding type relation std :: VARIADIC-REL _v < T, U ...> as specified in 21.3.7 [meta.rel] . 4 For any types or typedef-names T and R , pack of types or typedef-names U ... , and range r such that ranges :: to < vector >( r ) == vector {^ U ...} is true , for each ternary function template std :: meta :: type_ VARIADIC-REL-R defined in this clause, std :: meta :: type_ VARIADIC-REL-R (^ R, ^ T, r ) equals the value of the corresponding type relation std :: VARIADIC-REL-R _v < R, T, U ...> as specified in 21.3.7 [meta.rel] . consteval bool type_is_same ( info type1, info type2 ) ; consteval bool type_is_base_of ( info type_base, info type_derived ) ; consteval bool type_is_convertible ( info type_src, info type_dst ) ; consteval bool type_is_nothrow_convertible ( info type_src, info type_dst ) ; consteval bool type_is_layout_compatible ( info type1, info type2 ) ; consteval bool type_is_pointer_interconvertible_base_of ( info type_base, info type_derived ) ; template < reflection_range R = initializer_list < info >> consteval bool type_is_invocable ( info type, R && type_args ) ; template < reflection_range R = initializer_list < info >> consteval bool type_is_invocable_r ( info type_result, info type, R && type_args ) ; template < reflection_range R = initializer_list < info >> consteval bool type_is_nothrow_invocable ( info type, R && type_args ) ; template < reflection_range R = initializer_list < info >> consteval bool type_is_nothrow_invocable_r ( info type_result, info type, R && type_args ) ; 5 [  Note 1: If t is a reflection of the type int and u is a reflection of an alias to the type int , then t == u is false but type_is_same ( t, u ) is true . t == dealias ( u ) is also true . —  end note  ] .

[meta.reflection.trans], Transformations between types

1 Subclause [meta.reflection.trans] contains consteval functions that may be used to transform one type to another following some predefined rule.
1 For any type or typedef-name T , for each function std :: meta :: type_ MOD defined in this clause, std :: meta :: type_ MOD (^ T ) returns the reflection of the corresponding type std :: MOD _t < T > as specified in 21.3.8.2 [meta.trans.cv] . consteval info type_remove_const ( info type ) ; consteval info type_remove_volatile ( info type ) ; consteval info type_remove_cv ( info type ) ; consteval info type_add_const ( info type ) ; consteval info type_add_volatile ( info type ) ; consteval info type_add_cv ( info type ) ;
1 For any type or typedef-name T , for each function std :: meta :: type_ MOD defined in this clause, std :: meta :: type_ MOD (^ T ) returns the reflection of the corresponding type std :: MOD _t < T > as specified in 21.3.8.3 [meta.trans.ref] . consteval info type_remove_reference ( info type ) ; consteval info type_add_lvalue_reference ( info type ) ; consteval info type_add_rvalue_reference ( info type ) ;
1 For any type or typedef-name T , for each function std :: meta :: type_ MOD defined in this clause, std :: meta :: type_ MOD (^ T ) returns the reflection of the corresponding type std :: MOD _t < T > as specified in 21.3.8.4 [meta.trans.sign] . consteval info type_make_signed ( info type ) ; consteval info type_make_unsigned ( info type ) ;
1 For any type or typedef-name T , for each function std :: meta :: type_ MOD defined in this clause, std :: meta :: type_ MOD (^ T ) returns the reflection of the corresponding type std :: MOD _t < T > as specified in 21.3.8.5 [meta.trans.arr] . consteval info type_remove_extent ( info type ) ; consteval info type_remove_all_extents ( info type ) ;
1 For any type or typedef-name T , for each function std :: meta :: type_ MOD defined in this clause, std :: meta :: type_ MOD (^ T ) returns the reflection of the corresponding type std :: MOD _t < T > as specified in 21.3.8.6 [meta.trans.ptr] . consteval info type_remove_pointer ( info type ) ; consteval info type_add_pointer ( info type ) ;

[ Editor's note: There are four transformations that are deliberately omitted here. type_identity and enable_if are not useful, conditional(cond, t, f) would just be a long way of writing cond ? t : f , and basic_common_reference is a class template intended to be specialized and not directly invoked. ]

1 For any type or typedef-name T , for each function std :: meta :: type_ MOD defined in this clause with signature std :: meta :: info ( std :: meta :: info ) , std :: meta :: type_ MOD (^ T ) returns the reflection of the corresponding type std :: MOD _t < T > as specified in 21.3.8.7 [meta.trans.other] . 2 For any pack of types or typedef-names T ... and range r such that ranges :: to < vector >( r ) == vector {^ T ...} is true , for each unary function template std :: meta :: type_ VARIADIC-MOD defined in this clause, std :: meta :: type_ VARIADIC-MOD ( r ) returns the reflection of the corresponding type std :: VARIADIC-MOD _t < T ...> as specified in 21.3.8.7 [meta.trans.other] . 3 For any type or typedef-name T , pack of types or typedef-names U ... , and range r such that ranges :: to < vector >( r ) == vector {^ U ...} is true , std :: meta :: type_invoke_result (^ T, r ) returns the reflection of the corresponding type std :: invoke_result_t < T, U ...> ( 21.3.8.7 [meta.trans.other] ). consteval info type_remove_cvref ( info type ) ; consteval info type_decay ( info type ) ; template < reflection_range R = initializer_list < info >> consteval info type_common_type ( R && type_args ) ; template < reflection_range R = initializer_list < info >> consteval info type_common_reference ( R && type_args ) ; consteval info type_underlying_type ( info type ) ; template < reflection_range R = initializer_list < info >> consteval info type_invoke_result ( info type, R && type_args ) ; consteval info type_unwrap_reference ( info type ) ; consteval info type_unwrap_ref_decay ( info type ) ; 4 [  Example 1: // example implementation consteval info type_unwrap_reference ( info type ) { type = dealias ( type ) ; if ( has_template_arguments ( type ) && template_of ( type ) == ^ reference_wrapper ) { return type_add_lvalue_reference ( template_arguments_of ( type )[ 0 ]) ; } else { return type; } } —  end example  ]

This is a feature with both a language and library component. Our usual practice is to provide something like __cpp_impl_reflection and __cpp_lib_reflection for this. But since the two pieces are so closely tied together, maybe it really only makes sense to provide one?

For now, we’ll add both.

To 15.11 [cpp.predefined] :

__cpp_impl_coroutine 201902L __cpp_impl_destroying_delete 201806L __cpp_impl_three_way_comparison 201907L + __cpp_impl_reflection 2024XXL

and 17.3.2 [version.syn] :

+ #define __cpp_lib_reflection 2024XXL // also in <meta>
  • Stack Overflow for Teams Where developers & technologists share private knowledge with coworkers
  • Advertising & Talent Reach devs & technologists worldwide about your product, service or employer brand
  • OverflowAI GenAI features for Teams
  • OverflowAPI Train & fine-tune LLMs
  • Labs The future of collective knowledge sharing
  • About the company Visit the blog

Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Get early access and see previews of new features.

Convert string_view to char* and walk away with it

In this post, How you convert a std::string_view to a const char*? , the question was raised how to convert std::string_view into char* and the answer is basically:

make a temporary string from the view (makes a copy) and use its c_str()

That works if you're not planning on keeping the char* beyond the lifetime of the string. However, I do want to keep it. Hence, in contrast to the linked Q&A, I have to copy the underlying string (thus copying the memory a second time).

Moreover, I am settled for unique_ptr<char[]> instead of std::string due to its size.

What is the best way to do this conversion from string_view to unique_ptr<char[]> ?

First Try : The intuitive first approach is to just make a copy of the temporary c_str() , but now I've copied my character array twice: once to create the temporary string, and another time to make a permanent char* that survives the destruction of my temporary string.

Second Try : Maybe I can somehow std::move out the temporary string into my char* ? To this end, I'd have to assign the string's buffer to it, and somehow tell the string that it no longer owns its buffer, but I have no idea how that might work.

Third Try : I could skip the temporary string altogether and just create my char* manually with one more character than the string_view for the zero-termination. However, this seems clunky and error prone, and surely not the intended way of doing that, is it?

So, my question is: is this really the intended way of converting a string_view to char* (or derivatives)?

EDIT: for anyone wondering why I prefer a char* (or the conceptually equal unique_ptr<char[]> ) over a std::string : I have a large number of std::variant s that almost always hold an integer ( uintptr_t ) and, occasionally hold a text. Thus, it would be a colossal waste of space were it to be able to hold a std::string .

  • string-view

Remy Lebeau's user avatar

  • 2 is this really the intended way of converting a string_view to char* ? No. –  Eljay Commented yesterday
  • 2 What's wrong with keeping that std::string as a long-term storage for the characters? Why do you want to copy them elsewhere? –  HolyBlackCat Commented yesterday
  • 3 Then your last snippet already looks optimal to me. –  HolyBlackCat Commented yesterday
  • 3 a char* is just a pointer. If you want to keep the string longer than the original around you need to make a copy, no way to avoid that –  463035818_is_not_an_ai Commented yesterday
  • 4 In all your considerations, you never considered that the std::string_view might not be a view over an 0x00 terminated char[] . What if it isn’t? What if it is … and so on. Getting the char* it is viewing is the easy part. Using that char* as an 0x00 terminated char[] is the more difficult part because you have to make the assumption. You can check before using it though. –  Chukwujiobi Canon Commented yesterday

Know someone who can answer? Share a link to this question via email , Twitter , or Facebook .

Your answer.

Reminder: Answers generated by artificial intelligence tools are not allowed on Stack Overflow. Learn more

Sign up or log in

Post as a guest.

Required, but never shown

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy .

Browse other questions tagged c++ c-strings string-view or ask your own question .

  • The Overflow Blog
  • Navigating cities of code with Norris Numbers
  • Featured on Meta
  • We've made changes to our Terms of Service & Privacy Policy - July 2024
  • Bringing clarity to status tag usage on meta sites
  • Feedback requested: How do you use tag hover descriptions for curating and do...

Hot Network Questions

  • How to allow just one user to use SSH?
  • Function to find the most common numeric ordered pairings (value, count)
  • Giant War-Marbles of Doom: What's the Biggest possible Spherical Vehicle we could Build?
  • Why does characteristic equation and DC model equation for drain current in JFETs do not agree?
  • How can I cover all my skin (face+neck+body) while swimming outside (sea or outdoor pool) to avoid UV radiations?
  • conflict of \counterwithin and cleveref package when sectioncounter is reset
  • In compound nouns is there a way to distinguish which is the subject or the object?
  • Is “overaction” an Indian English word?
  • Unknown tool. Not sure what this tool is, found it in a tin of odds and ends
  • The complement of a properly embedded annulus in a handlebody is a handlebody
  • Column generation for bin packing problem with variable size minimizing the largest cost of any class
  • when translating a video game's controls into German do I assume a German keyboard?
  • Web App - Set a Max Width or Let it Expand Responsively?
  • Is Hilbert's epsilon calculus a conservative extension of intuitionist logic?
  • How to cite a book if only its chapters have DOIs?
  • How would a culture living in an extremely vertical environment deal with dead bodies?
  • Is it mandatory in German to use the singular in negative sentences like "none of the books here are on fire?"
  • Short story or novella where a man's wife dies and is brought back to life. The process is called rekindling. Rekindled people are very different
  • Guitar amplifier placement for live band
  • Where exactly was this picture taken?
  • How does static resources help prevent security vulnerabilities?
  • Did the United States have consent from Texas to cede a piece of land that was part of Texas?
  • Bottom bracket: What to do about rust in cup + blemish on spindle?
  • What can cause a 24 volt solar panel to output 40 volt?

std unique_ptr move assignment

IMAGES

  1. C++ : how to move an std::unique_ptr from one STL container to another

    std unique_ptr move assignment

  2. [Solved] What happens to unique_ptr after std::move()?

    std unique_ptr move assignment

  3. C++ : How to capture std::unique_ptr "by move" for a lambda in std::for

    std unique_ptr move assignment

  4. C++ : How to correctly move ownership from raw pointer to std::unique

    std unique_ptr move assignment

  5. 【C++

    std unique_ptr move assignment

  6. Unique_ptr Move

    std unique_ptr move assignment

COMMENTS

  1. What happens to unique_ptr after std::move ()?

    The standard says (§ 20.8.1.2.1 ¶ 16, emphasis added) that the move constructor of std::unique_ptr. unique_ptr(unique_ptr&& u) noexcept; Constructs a unique_ptr by transferring ownership from u to *this. Therefore, after you move-construct the temporary object that gets passed as argument to attachActor form your tony, tony no longer owns the ...

  2. std::unique_ptr

    class T, class Deleter. std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. the managing unique_ptr object is destroyed. the managing unique_ptr object is assigned another pointer via operator= or reset () .

  3. c++

    std::move creates a RValue Reference from unique_ptr. function_call_move takes a RValue Reference, but until a RValue Reference assignment operator or constructor is used to steal the unique_ptr's information it doesn't get harmed. Essentially just because you can mug it and steal it's info doesn't mean you have to.

  4. Copying and Moving unique_ptr in C++

    In Modern C++, the Smart Pointer unique_ptr ensures that only one smart pointer can manage a given resource at a time. You cannot copy a unique_ptr but you can transfer its ownership to another unique_ptr using std::move().

  5. 22.5

    Learn how to use std::unique_ptr, a smart pointer that owns and manages another object, in C++. See examples, benefits, and pitfalls of this feature.

  6. How to: Create and use unique_ptr instances

    unique_ptr is defined in the <memory> header in the C++ Standard Library. It is exactly as efficient as a raw pointer and can be used in C++ Standard Library containers. The addition of unique_ptr instances to C++ Standard Library containers is efficient because the move constructor of the unique_ptr eliminates the need for a copy operation.

  7. std::unique_ptr

    If the default deleter is used, T must be complete at the point in code where the deleter is invoked, which happens in the destructor, move assignment operator, and reset member function of std::unique_ptr.

  8. std::unique_ptr<T,Deleter>:: unique_ptr

    5)Constructs a unique_ptrby transferring ownership from uto *thisand stores the null pointer in u. This constructor only participates in overload resolution if std::is_move_constructible<Deleter>::valueis true. If Deleteris not a reference type, requires that it is nothrow-MoveConstructible(if Deleteris a reference, get_deleter()and u.get_deleter()after move construction reference the same value).

  9. unique_ptr

    The specialization of unique_ptr for arrays with runtime length ( unique_ptr<T [],D>) does not support constructors (7) and (8), and does not accept types convertible to pointer (except pointer itself) as argument p (most notably pointers to types derived from element_type are not accepted).

  10. std::unique_ptr<T,Deleter>::operator=

    As a move-only type, unique_ptr 's assignment operator only accepts rvalues arguments (e.g. the result of std::make_unique or a std::move 'd unique_ptr variable).

  11. unique_ptr

    unique_ptr assignment The object acquires the ownership of x 's content, including both the stored pointer and the stored deleter (along with the responsibility of deleting the object at some point). Any object owned by the unique_ptr object before the call is deleted (as if unique_ptr's destructor was called).

  12. How to Transfer unique_ptrs From a Set to Another Set

    The purpose of the std::move algorithm is to avoid making copies on the unique_ptr elements and move them instead, so why are they being copied?? The answer lies in how the set provides access to its elements.

  13. Unique_ptr in C++

    std::unique_ptr is a smart pointer introduced in C++11. It automatically manages the dynamically allocated resources on the heap. Smart pointers are just wrappers around regular old pointers that help you prevent widespread bugs. Namely, forgetting to delete a pointer and causing a memory leak or accidentally deleting a pointer twice or in the ...

  14. Understanding unique_ptr with Example in C++11

    Brief Prior to C++11, the standard provided std::auto_ptr. Which had some limitations. But from C++11, standard provided many smart pointers classes. Understanding unique_ptr with example in C++ requires an understanding of move semantics which I have discussed here & here.

  15. Using std::unique_ptr and std::move

    std::unique_ptr<Inproc> create(); Avoid functions such as create, init, destroy, copy and clone. C++ has special member functions for that: Constructor, destructor and copy assignment. They cannot always be used, but if they can prefer them. Inpoc::create should actually be a regular constructor.

  16. std::unique_ptr<T,Deleter>:: reset

    1) Given current_ptr, the pointer that was managed by *this, performs the following actions, in this order:

  17. Move Constructor & Assignment Operator With std::shared_ptr

    In an earlier article, we have seen how move constructor & move assignment operators helped us in creating our own unique_ptr. Here we will use move constructor & assignment operator to implement unsophisticated shared_ptr.

  18. c++11

    I have a problem very similar to How to make a class with a member of unique_ptr work with std::move and std::swap? and I am using VS2013 so I expect the following to work. #include <memory&...

  19. Does assigning make_unique require std::move() to an empty unique_ptr?

    Is std::move required to assign a temporary unique_ptr to an empty unique_ptr? Both cases can be compiled successfully but I wonder if there is any difference between them.

  20. Reflection for C++26

    adding a number of missing predicates: is_enumerator, is_copy_constructor, is_move_constructor, is_assignment, is_move_assignment, is_copy_assignment ... collapsed entity_ref and pointer_to_member into value_of; ... of program elements via constant-expressions producing reflection values — reflections for short — of an opaque type std ...

  21. assigning an unique_ptr with std::move not working

    1. When you do x = std::move(y) And y is of type std::unique_ptr<T>, then x must also be of type std::unique_ptr<T> (or two types whose pointers are implicitly convertible) or the assignment won't work. Your code, as shown, can only work if T is int8_t because that's what's on the left. edited May 7, 2020 at 22:03. answered May 7, 2020 at 20:32.

  22. Convert string_view to char* and walk away with it

    EDIT: for anyone wondering why I prefer a char* (or the conceptually equal unique_ptr<char[]>) over a std::string: I have a large number of std::variants that almost always hold an integer (uintptr_t) and, occasionally hold a text. Thus, it would be a colossal waste of space were it to be able to hold a std::string.