Updated the tile and the description locally to:
    libstdc++: Value-initialize _Hashtable_ebo_helper members [PR126949]

    The no_unique_address _Hashtable_ebo_helper members (that replaced base
    classes in r15-6273-g689d4abc0b836a), are initialized with {}. Since the
    _Hashtable_ebo_helper are aggregates, the "{}" was performing aggregate
    initialization. In particular such initialization is ill-formed, if any
    aggregate member has explicit default constructor, as they cannot be
    initialized from {} in such case. This introduced and regression, where
    hasher, equality and allocators with explicit default constructor were
    no longer supported.

    This patch addresses above by properly value-initializing such members,
    using = Type() syntax, which in turn value-initializes all aggregate
    members, and calls explicit default constructors.

On Thu, Sep 10, 2026 at 11:40 AM Tomasz Kamiński <[email protected]>
wrote:

> The no_unique_address _Hashtable_ebo_helper members (that replaced
> base classes in r15-6273-g689d4abc0b836a), are initialized with {}.
> Since the _Hashtable_ebo_helper are aggregates, the "{}" was performing
> aggregate initializtion, and not value initialization. In particular such
> aggregate initialization is ill-formed, if members has explicit default
> constructor, as they cannot be initialized from {} in such case.
> This introduced and regression, when hasher, equality and alloators
> with explict default constructor are no longer supported.
>
> This patch addreses above by properly value-initializing such members,
> using = Type() syntax, which in turn value-initializes all members,
> and accepts explicit default constructors.
>
> libstdc++-v3/ChangeLog:
>
>         PR libstdc++/126949
>         * include/bits/hashtable_policy.h (_Hash_code_base::_M_hash)
>         (_Hashtable_base::_M_equal, _Hashtable_alloc::_M_alloc):
>         Value initialized members in NSDMI.
>         * testsuite/23_containers/unordered_map/cons/default.cc: Check
>         default construction with an hash, equality and allocator with
>         explicit constructor.
>         * testsuite/23_containers/unordered_set/cons/default.cc: Likewise.
>
> Co-authored-by: MarcoFalke <[email protected]>
> Signed-off-by: Tomasz Kamiński <[email protected]>
> ---
> v2:
>  - changes approach to fix, by value initializing _Hashtable_ebo_helper
>  - expands test to cover equality and allocator with explicit default
>    constructor
>
> Testing on x86_64_linux. Tested modified test with all standard modes,
> debug, assertions and -m32. OK for trunk/16/15 when all test passes?
>
>  libstdc++-v3/include/bits/hashtable_policy.h  |  9 ++--
>  .../unordered_map/cons/default.cc             | 49 +++++++++++++++++++
>  .../unordered_set/cons/default.cc             | 48 ++++++++++++++++++
>  3 files changed, 103 insertions(+), 3 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/hashtable_policy.h
> b/libstdc++-v3/include/bits/hashtable_policy.h
> index 6e4b365d592..84ebbf65bcb 100644
> --- a/libstdc++-v3/include/bits/hashtable_policy.h
> +++ b/libstdc++-v3/include/bits/hashtable_policy.h
> @@ -1074,7 +1074,8 @@ namespace __detail
>        { return _M_hash._M_obj; }
>
>      protected:
> -      [[__no_unique_address__]] _Hashtable_ebo_helper<_Hash> _M_hash{};
> +      [[__no_unique_address__]]
> +      _Hashtable_ebo_helper<_Hash> _M_hash =
> _Hashtable_ebo_helper<_Hash>();
>
>        using __hash_code = size_t;
>
> @@ -1411,7 +1412,8 @@ namespace __detail
>        using __hash_code = typename __hash_code_base::__hash_code;
>
>      protected:
> -      [[__no_unique_address__]] _Hashtable_ebo_helper<_Equal> _M_equal{};
> +      [[__no_unique_address__]]
> +      _Hashtable_ebo_helper<_Equal> _M_equal =
> _Hashtable_ebo_helper<_Equal>();
>
>        _Hashtable_base() = default;
>
> @@ -1491,7 +1493,8 @@ namespace __detail
>      struct _Hashtable_alloc
>      {
>      private:
> -      [[__no_unique_address__]] _Hashtable_ebo_helper<_NodeAlloc>
> _M_alloc{};
> +      [[__no_unique_address__]]
> +      _Hashtable_ebo_helper<_NodeAlloc> _M_alloc =
> _Hashtable_ebo_helper<_NodeAlloc>();
>
>        template<typename>
>         struct __get_value_type;
> diff --git
> a/libstdc++-v3/testsuite/23_containers/unordered_map/cons/default.cc
> b/libstdc++-v3/testsuite/23_containers/unordered_map/cons/default.cc
> index 7a785e980b1..2a65253c240 100644
> --- a/libstdc++-v3/testsuite/23_containers/unordered_map/cons/default.cc
> +++ b/libstdc++-v3/testsuite/23_containers/unordered_map/cons/default.cc
> @@ -49,3 +49,52 @@ Map4 m4{m};
>  Map4 m5{m, a};
>  Map4 m6{std::move(m)};
>  Map4 m7{std::move(m6), a};
> +
> +// PR libstdc++/126949
> +struct ExplicitHash
> +{
> +  explicit ExplicitHash(bool = false) { }
> +
> +  std::size_t operator()(int value) const
> +  { return value; }
> +};
> +
> +struct ExplicitEqual
> +{
> +  explicit ExplicitEqual(bool = false) { }
> +
> +  bool operator()(int lhs, int rhs) const
> +  { return lhs == rhs; }
> +};
> +
> +template<typename T>
> +  struct ExplicitAlloc
> +  {
> +    using value_type = T;
> +
> +    explicit ExplicitAlloc(bool = false) noexcept { }
> +
> +    template<typename U>
> +      ExplicitAlloc(const ExplicitAlloc<U>&) { }
> +
> +    T *allocate(std::size_t n)
> +    { return std::allocator<T>().allocate(n); }
> +
> +    void deallocate(T *p, std::size_t n)
> +    { std::allocator<T>().deallocate(p, n); }
> +
> +    bool operator==(const ExplicitAlloc&) const { return true; }
> +    bool operator!=(const ExplicitAlloc&) const { return false; }
> +  };
> +
> +void
> +test_hash()
> +{
> +  std::unordered_map<int, int, ExplicitHash> map1;
> +  std::unordered_map<int, int, std::hash<int>, ExplicitEqual> map2;
> +  std::unordered_map<int, int, std::hash<int>, std::equal_to<int>,
> +                              ExplicitAlloc<std::pair<const int, int>>>
> map3;
> +  std::unordered_map<int, int, ExplicitHash, ExplicitEqual,
> +                              ExplicitAlloc<std::pair<const int, int>>>
> map4;
> +
> +}
> diff --git
> a/libstdc++-v3/testsuite/23_containers/unordered_set/cons/default.cc
> b/libstdc++-v3/testsuite/23_containers/unordered_set/cons/default.cc
> index fb87c96ce9d..049b8059cdb 100644
> --- a/libstdc++-v3/testsuite/23_containers/unordered_set/cons/default.cc
> +++ b/libstdc++-v3/testsuite/23_containers/unordered_set/cons/default.cc
> @@ -48,3 +48,51 @@ Set4 s4{s};
>  Set4 s5{s, a};
>  Set4 s6{std::move(s)};
>  Set4 s7{std::move(s6), a};
> +
> +// PR libstdc++/126949
> +struct ExplicitHash
> +{
> +  explicit ExplicitHash(bool = false) { }
> +
> +  std::size_t operator()(int value) const
> +  { return value; }
> +};
> +
> +struct ExplicitEqual
> +{
> +  explicit ExplicitEqual(bool = false) { }
> +
> +  bool operator()(int lhs, int rhs) const
> +  { return lhs == rhs; }
> +};
> +
> +template<typename T>
> +  struct ExplicitAlloc
> +  {
> +    using value_type = T;
> +
> +    explicit ExplicitAlloc(bool = false) noexcept { }
> +
> +    template<typename U>
> +      ExplicitAlloc(const ExplicitAlloc<U>&) { }
> +
> +    T *allocate(std::size_t n)
> +    { return std::allocator<T>().allocate(n); }
> +
> +    void deallocate(T *p, std::size_t n)
> +    { std::allocator<T>().deallocate(p, n); }
> +
> +    bool operator==(const ExplicitAlloc&) const { return true; }
> +    bool operator!=(const ExplicitAlloc&) const { return false; }
> +  };
> +
> +void
> +test_explicit_hash()
> +{
> +  std::unordered_set<int, int, ExplicitHash> set1;
> +  std::unordered_set<int, int, std::hash<int>, ExplicitEqual> set2;
> +  std::unordered_set<int, int, std::hash<int>, std::equal_to<int>,
> +                              ExplicitAlloc<int>> set3;
> +  std::unordered_set<int, int, ExplicitHash, ExplicitEqual,
> +                              ExplicitAlloc<int>> set4;
> +}
> --
> 2.55.0
>
>

Reply via email to