Hashing meta::info

Document #: P3816R4 [Latest] [Status]
Date: 2026-10-22
Project: Programming Language C++
Audience: SG7 Reflection
Reply-to: Matt Cummins
<>
Valentyn Yukhymenko
<>

1 Revision history

1.1 Changes from R3:

1.2 Changes from R2:

1.3 Changes from R1:

1.4 Changes from R0:

2 Abstract

This paper proposes a new standard library template, consteval_hash<T>, with a single specialization for meta::info. The purpose of this facility is to provide a standard interface for compile-time hashing, thereby allowing unordered containers such as unordered_map and unordered_set to be used with meta::info keys, and potentially with other types in the future.

consteval auto compile_time_function() -> void
{
    const auto hasher = consteval_hash<meta::info>{}; // proposed
    const size_t h = hasher(^^::);

    // now possible
    unordered_map<meta::info, int, consteval_hash<meta::info>> m;
    unordered_set<meta::info, consteval_hash<meta::info>>      s;
}

3 Polls

3.1 EWG Review - Brno, Czech Republic (2026-06)

Any hashing feature in the direction of P3816 should require cross-TU hash stability (with caveats for version drift).

SF F N A SA
9 16 0 0 0

Result: consensus

EWG encourage the authors to explore the possibility of fully specifying the hash result.

SF F N A SA
1 2 9 4 2

Result: not consensus

EWG encourages more work in the direction of P3816R3.

SF F N A SA
5 18 1 0 0

Result: consensus

3.2 EWG Review - Croydon, UK (2026-03)

Encourage more work in the direction of P3816R2 (Hashing meta::info), and see it again after incorporating the feedback received/better exploring the stability guarantees.

SF F N A SA
7 22 1 0 0

Result: consensus

4 Motivation

[P2996] introduces meta::info to represent reflections of C++ constructs. Hashing support was intentionally omitted, as it lies outside the core reflection feature.

[P3372] makes unordered associative containers constexpr. This creates a need for compile-time hashing, including support for meta::info and other consteval-only types as keys.

A robust hash for meta::info requires compiler support; we therefore believe such a facility belongs in the standard library.

Given that this proposal focuses on enabling unordered associative containers keyed on meta::info, it is natural to also ask what meaning, if any, should be assigned to map<meta::info, T> and set<meta::info>. Both would require less<meta::info>, and hence operator<, to be well-defined. However, reflections do not admit a natural or semantically meaningful total ordering. In addition, [P2830] (4.1.4) states that any operator<=> defined for std::meta::info should be consistent with the compile-time type ordering it proposes.

Ultimately, defining such an ordering introduces additional semantic challenges and design complexity, so this proposal intentionally restricts its scope to hashing and leaves ordering of reflections for future work. Readers interested in this should see [P4032].

Enabling hash-based associative containers as a first step will improve the ergonomics of compile-time programming and bring it more in line with runtime code.

In terms of design, a straightforward approach would be to specialize hash<meta::info>, but hash<T> in general is not constexpr-friendly due to its runtime requirements, so it would be inconsistent with compile-time hashing of other types. As such, this paper proposes a dedicated facility for compile-time hashing which can be extended to other types in the future.

5 Examples

The following examples illustrate practical applications of consteval_hash.

5.1 Mp11 mp_unique using reflection

In [P2830] (7.3) the authors discuss the infeasibility of implementing mp_unique using value-based reflection. Our proposal provides a short and effective solution without needing to sort a list of reflected types.

template <typename... Types>
struct type_list {};

template <typename TypeList>
consteval auto mp_unique_reflected()
{
    static_assert(meta::has_template_arguments(^^TypeList), "mp_unique requires a type_list");
    static_assert(meta::template_of(^^TypeList) == ^^type_list, "mp_unique requires a type_list");

    unordered_set<meta::info, consteval_hash<meta::info>> seen;
    vector<meta::info> unique_types;

    for (auto type_info : meta::template_arguments_of(^^TypeList)) {
        if (const bool is_unique = seen.insert(type_info).second; is_unique) {
            unique_types.push_back(type_info);
        }
    }

    return meta::substitute(^^type_list, unique_types);
}

template <class TypeList>
using mp_unique = [:mp_unique_reflected<TypeList>():];

using input = type_list<int, char, int, string, double, char>;
using filtered = mp_unique<input>;
using expected = type_list<int, char, string, double>;

static_assert(is_same_v<expected, filtered>);

5.2 Custom name_of function

In [P2996] (4.4.6), the authors discuss the challenges of producing user-friendly names for reflected entities and argue that functions such as name_of should be implemented by third-party C++ libraries rather than standardized, with the standard providing the necessary lower-level tools to do so. To implement such a function, one might define a custom mapping of known types or entities to more descriptive labels.

Using unordered_map for the mapping provides a terser implementation:

Without map
With map
consteval auto name_of(meta::info r) -> string_view
{
    if (r == ^^int) {
        return "integer";
    }
    if (r == ^^float) {
        return "32-bit float";
    }
    if (r == ^^double) {
        return "64-bit float";
    }
    if (r == ^^bool) {
        return "boolean";
    }
    if (r == ^^unsigned) {
        return "unsigned integer";
    }
    if (r == ^^::) {
        return "global namespace";
    }
    if (r == ^^MyType) {
        return "my library type";
    }
    // add more as required

    // Fall back to identifier if it exists
    if (meta::has_identifier(r)) {
        return meta::identifier_of(r);
    }
    return "<unnamed>";
}
consteval auto name_of(meta::info r) -> string_view
{
    unordered_map<meta::info, string_view, consteval_hash<meta::info>> names = {
        {^^int,      "integer"},
        {^^float,    "32-bit float"},
        {^^double,   "64-bit float"},
        {^^bool,     "boolean"},
        {^^unsigned, "unsigned integer"},
        {^^::,       "global namespace"},
        {^^MyType,   "my library type"},
        // add more as required
    };

    if (auto it = names.find(r);
        it != names.end()) {
        return it->second;
    }

    // Fall back to identifier if it exists
    if (meta::has_identifier(r)) {
        return meta::identifier_of(r);
    }
    return "<unnamed>";
}

5.3 Annotations by type

[P3394] introduces annotations for reflection and provides two query functions:

consteval vector<info> annotations_of(info item);
consteval vector<info> annotations_of_with_type(info item, info type);

The second function is provided because grouping annotations by their type is a common operation. If a standard compile-time hashing facility was available, then for cases that require calling this function several times with different types, it might be more ergonomic to have an additional function:

consteval unordered_map<info, vector<info>, consteval_hash<info>> annotations_of_by_type(info item);

6 Impact

6.1 On the Standard

This proposal is a pure library addition and does not depend on any other library extensions. It introduces the first example of a compile-time hash facility. Moreover, it enables future standard library features: new consteval functions may naturally require unordered containers as part of their interfaces, and consteval_hash provides the uniform mechanism needed to support such APIs.

6.2 On existing code

As this is a new type in the std namespace, this change does not break any existing code, except in the unlikely event that a user has added their own consteval_hash type to std, which is already undefined behavior.

6.3 On ABI

This proposal requires that a given reflection hashes to the same value in every translation unit of a program, so a hash value baked into a header means the same thing everywhere it is included.

Since translation units are compiled separately, meeting that requirement means producing the same value on repeated compilations. The standard has no notion of repeated compilations, so we state this as a strong recommendation rather than a requirement. An implementation ignoring it could link translation units that disagree on a hash value, giving the usual ABI mismatch. We consider this acceptable: reflection already admits the same hazard, as shown in Appendix B.

6.4 On undefined behavior

This proposal applies only to compile-time programming, where undefined behavior is disallowed. Accordingly, it does not introduce additional undefined behavior into the standard.

7 Design decisions

7.1 The API

7.1.1 hash<meta::info>

Ultimately, the goal of this paper is to provide a robust way to hash values of type meta::info. The most “obvious” way to do this is to implement hash<meta::info>, but this introduces inconsistencies:

7.1.2 hash_of

We could sidestep the issues associated with hash<meta::info> by instead providing a free function:

namespace std::meta {
    consteval auto hash_of(info r) -> size_t;
}

This would be defined in <meta>. If users need to use meta::info as keys in compile-time hash maps, they can use it to implement their own hash (as they must currently do for all other types). However, we do not propose this because:

7.1.3 consteval_hash<meta::info>

This brings us to the proposal in this paper. It has a few benefits:

It also has a few obvious downsides:

Overall, we believe this is the more complete and extensible solution, so it is the one we are proposing. To see discussion on other names for this type, see Appendix A.

7.2 Stability and specification of the hash algorithm

EWG polled that any hashing feature in this direction should require stability across translation units, with caveats for version drift. Feedback from implementers agreed that hash values should be stable, but cautioned against turning that stability into a strong ABI commitment.

Accordingly, this paper proposes to:

8 Proposed wording

8.1 The specification for consteval_hash<T>

Add a new section, ConstevalHash [constevalhash.requirements], defined analogously to Cpp17Hash [hash.requirements]:

A type H meets the ConstevalHash requirements if:

  • 1 It is a function object type ([function.objects]).
  • 2 It meets the Cpp17CopyConstructible and Cpp17Destructible requirements.
  • 3 It is a consteval-only type ([basic.types.general]).
  • 4 The expressions in the table below are valid and have the indicated semantics:

Given Key is an argument type for function objects of type H, h is a value of type (possibly const) H, u is an lvalue of type Key, and k is a value of type convertible to (possibly const) Key:

Expression Return type Requirement
h(k) size_t

The value returned shall depend only on k.

It is a constant expression.

For two different values t1 and t2, the probability that h(t1) and h(t2) compare equal should be very small, approaching 1.0 / numeric_limits<size_t>::max().

[ Note: Evaluations of h(k) in different translation units of the same program yield the same value. — end note ]

h(u) size_t Shall not modify u.

Add a new section, “Class template consteval_hash” [unord.consteval_hash], defined analogously to “Class template hash” [unord.hash]. The only difference is that the proposed wording currently makes no mention of specializations for nullptr_t or for cv-unqualified arithmetic, enumeration, and pointer types (which can be added later):

Class template consteval_hash

  • 1 Each specialization of consteval_hash is either enabled or disabled, as described below.
    • (1.1) [ Note: Enabled specializations meet the ConstevalHash requirements, and disabled specializations do not. — end note ]
  • 2 If the library provides an explicit or partial specialization of consteval_hash<Key>, that specialization is enabled except as noted otherwise, and its member functions are noexcept except as noted otherwise.
  • 3 If H is a disabled specialization of consteval_hash, these values are false: is_default_constructible_v<H>, is_copy_constructible_v<H>, is_copy_assignable_v<H>, and is_move_assignable_v<H>. Disabled specializations of consteval_hash are not function object types ([function.objects]).
    • (3.1) [ Note: This means that the specialization of consteval_hash exists, but any attempt to use it as a ConstevalHash is ill-formed. — end note ]
  • 4 An enabled specialization consteval_hash<Key> shall:
    • (4.1) Meet the ConstevalHash requirements with Key as the function call argument type, the Cpp17DefaultConstructible requirements, the Cpp17CopyAssignable requirements, and the Cpp17Swappable requirements.
    • (4.2) Meet the requirement that if k1 == k2 is true, h(k1) == h(k2) is also true, where h is an object of type consteval_hash<Key> and k1 and k2 are objects of type Key.
    • (4.3) Meet the requirement that the expression h(k), where h is an object of type consteval_hash<Key> and k is an object of type convertible to Key, shall not throw an exception unless consteval_hash<Key> is a program-defined specialization.

8.2 The specialization for meta::info

Add a new section [meta.reflection.hash]:

namespace std {
  template<class T> struct consteval_hash;
  template<> struct consteval_hash<meta::info>;
}

The specialization is enabled ([unord.consteval_hash]).

8.3 Open question: which header declares consteval_hash<T>?

Note that consteval_hash is a member of namespace std, not std::meta; only the specialization for meta::info is specified in the reflection clause. Which header declares the primary template is left as an open question for LEWG:

We have a mild preference for the former, but do not think the choice affects the rest of the design.

8.4 Feature testing macros

Add two new feature macros to [version.syn], one for the new type, and one for the meta::info specialization:

#define __cpp_lib_consteval_hash           YYYYMML // also in <meta>
#define __cpp_lib_consteval_hash_meta_info YYYYMML // also in <meta>

9 Implementation experience

Like with the rest of the reflection API, consteval_hash<meta::info>::operator() can be implemented via a compiler intrinsic. For example:

template<>
struct consteval_hash<meta::info>
{
  consteval consteval_hash() = default;
  consteval consteval_hash(const consteval_hash<meta::info>&) = default;
  consteval consteval_hash(consteval_hash<meta::info>&&) = default;

  consteval auto operator()(meta::info r) const noexcept -> size_t {
    return __metafunction(meta::detail::__metafn_reflection_hash, r);
  }

private:

  // This unused variable is here to make consteval_hash<> a
  // consteval-only type.
  [[maybe_unused]] const meta::info unused = ^^::;
};

We are still working on a stable implementation. Feedback from GCC and Clang implementers indicates that the most practical strategy is to reuse existing mangling infrastructure to do the hashing.

[P3068] has been approved for C++26, allowing exception throwing within constexpr, so marking consteval_hash<meta::info>::operator() as noexcept is meaningful.

10 Possible extensions of the current design

10.1 More predefined consteval_hash<T> specializations

As mentioned above, consteval_hash<T> can be easily extended for all other standard and builtin types that have a specialization of std::hash<T>.

To improve initial usability and reduce unnecessary boilerplate for common cases, we could extend the scope of the original design by providing a small set of predefined specializations.

Specifically, we would propose adding predefined consteval_hash<T> specializations for:

These types are both widely used as keys in unordered associative containers and straightforward to support in a portable, implementation-independent manner.

Notably, extending initial support to pointer types would help resolve the implementation experience issues highlighted in [P3372] concerning std::hash<T*> having inconsistent values between compile-time and runtime. By separating compile-time hashing into a distinct type, this inconsistency is no longer a problem.

Adding support for other library types, in order to achieve feature parity with std::hash<T>, should be discussed in separate proposals, as not all currently supported types are available at compile-time and some may come with additional restrictions.

11 Acknowledgements

12 Appendix A: Alternate names for consteval_hash

There are a few other names we considered. Below is a list of them as well as the reasons we decided against them.

Name Comments
stable_hash<T> We feel this name would be more appropriate for a constexpr facility that is stable across both runtime and compile-time.
static_hash<T> The keyword static in C++ already means “compile-time” in some cases, e.g. static_assert, however the keyword is overloaded with many other meanings, so it could be confusing. consteval is the keyword for compile-time, hence consteval_hash.
meta_hash<T> Concise and clearly related to compile-time functionality. However, the word “meta” more closely relates to reflection, which is a subset of compile-time functionality, and one which compile-time hashing does not necessarily have anything to do with.
fixed_hash<T> Similar to stable_hash<T> in meaning.
compile_time_hash<T> This name best describes what it does, but given that C++ already has the consteval keyword to mean “compile-time-only”, this name is less consistent with the rest of the language.
comptime_hash<T> Although we love Zig, we are proposing a C++ feature!
ct_hash<T> Terse, but too terse. Would you guess that it was a shortening of “compile-time hash” at first glance?
ce_hash<T> Same as above. Would you guess it was a shortening of “consteval hash”?
compile_time_only_hash<T> Consistent with move_only_function, but far too long.
consteval_only_hash<T> A slight improvement on the above, but the “only” is superfluous since consteval already denotes that it only works at compile-time.
constexpr_hash<T> Just incorrect, implies that it is usable at runtime too. If such a type existed, you would expect its interface to be made up of constexpr functions, not consteval.
hash<T, hashtype::compile_time> Rather than a new type, we could instead extend hash<T> by providing an enum class to select what kind of hash you want. This enum could be extended to provide even more hashes in the future. This feels far messier, and given that it would be impossible to implement certain hashes for certain types, it would be misleading and provide an API that looks incomplete.

13 Appendix B: Stability semantics

One major point of discussion was the stability of produced hash values across translation units and repeated compiler runs. We considered three potential implementations.

13.1 Considered semantics

13.1.1 Unstable hash

This would produce a different hash value for a given object each time the compiler is run.

We have implemented this in Clang by hashing the AST node representing the reflected object.

13.1.2 Stable hash

For a stable hash, the values produced stay the same across repeated compiler runs.

13.1.3 Semi-stable hash

We attempted to find a middle ground that captures the best of both worlds. One such compromise is to ensure stability of values when recompiling the same source code, while still allowing those values to change when the source code itself changes. This approach was rejected because it is not stable across translation units.

We have two implementations of this. The first approach is more robust and relies on hashing certain properties of the underlying reflected construct. For example, for a namespace, we hash the source location. We are currently working on modifying this to create a stable hash. This can be done by relying on more stable properties of the reflected constructs; for example, using the name of a namespace rather than the source location.

The second approach is to take an otherwise unstable hash implementation and add a layer of interning: maintain a map from AST nodes (keyed by their pointer addresses) to integer IDs, assigning each ID from an incrementing counter. This yields a straightforward, semi-stable hash implementation. It also avoids the need for updates when new reflectable constructs are added to the language, since the mechanism depends solely on pointer identity rather than on the structure of the reflected entities.

13.2 Feedback from implementers

During discussion of R2 of the paper, we received feedback from compiler developers working on C++26 reflection implementations in Clang, EDG, and GCC.

EDG developers expressed a strong preference for making hash values as stable as possible and as specified as possible. Their main arguments were:

Clang developers agreed that hash values should be stable across translation units and compiler runs. The preferred implementation strategy is likely to hash an existing representation such as a mangled name.

This is considered the most practical approach, but it has limitations: not every reflected entity necessarily has a defined mangling, and hash values may differ between mangling schemes, such as Itanium and Microsoft ABI manglings. These limitations were not considered fundamental blockers, but they suggest that cross-implementation stability may be difficult to specify or guarantee.

Clang developers also raised concerns about making hash stability a strong ABI commitment. In practice, once ABI guarantees are provided, users tend to rely on them, making future changes difficult. Unspecified but stable hashing may therefore be the most realistic guarantee: stable across translation units and compiler runs, but not necessarily stable across compiler versions, ABI changes, or different implementations.

As a result, we received suggestions not to specify stability guarantees, but instead to keep them implementation-defined while providing recommendations about the preferred default semantics.

Feedback from GCC confirmed the preference for implementing stable hashing using mangling capabilities.

Overall, all three implementers agree that hash values should be stable across translation units and compiler runs. However, there are different opinions on whether this stability should be mandated by the standard or left as implementation-defined behavior, with implementers informally aligning on providing stability.

13.3 ABI example

As discussed, ABI concerns are a major reason for preferring a fully stable implementation. To see why, consider the following example:

// library.hpp
#include <meta>

constexpr auto buffer_size = std::consteval_hash<std::meta::info>{}(^^int) & 0xFFFF;

struct buffer
{
    char data[buffer_size];
};

buffer get_important_data();
// library.cpp
#include "library.hpp"
#include <print>

buffer get_important_data()
{
    auto buf = buffer{};
    std::print("size of buffer in library: {}\n", sizeof(buf));
    return buf;
}
// main.cpp
#include "library.hpp"
#include <print>

int main()
{
    const auto buf = get_important_data();
    std::print("size of buffer in main: {}\n", sizeof(buf));
};

Here, the buffer size depends on the hash of ^^int. With an implementation whose hash values differ between translation units, this program produces:

size of buffer in library: 25040
size of buffer in main: 40384

However, consteval_hash would not be the first reflection facility that makes it easy to introduce such an ABI problem. For instance, consider the above example again, but with the buffer size given by

constexpr auto buffer_size = std::meta::members_of(^^::, std::meta::access_context::current()).size();

Now the buffer size depends on how many members the global namespace has in each translation unit, which is exactly the same problem.

14 References

[P2830] Nate Nichols and Gašper Ažman. Constexpr Type Ordering.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2830r10.html
[P2996] Wyatt Childers, Peter Dimov, Dan Katz, Barry Revzin, Andrew Sutton, Faisal Vali, and Daveed Vandevoorde. Reflection for C++.
https://isocpp.org/files/papers/P2996R13.html
[P3068] Hana Dusíková. Allowing Exception Throwing in Constant-Evaluation.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3068r2.html
[P3372] Hana Dusíková. Constexpr Containers and Adaptors.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3372r3.html
[P3394] Wyatt Childers, Dan Katz, Barry Revzin, and Daveed Vandevoorde. Annotations for Reflection.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3394r4.html
[P4032] Lénárd Szolnoki. Strong ordering of meta::info.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p4032r0.html