Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[clang] fails to compile valid code involving variant and unordered_map with libstdc++ version <12 #84487

Copy link
Copy link
Open
@toughengineer

Description

@toughengineer
Issue body actions

tl;dr:

clang, starting from version 7.0.1 all the way up to the current trunk, in C++17 mode fails to compile the following code when combined with libstdc++ version <12:
https://godbolt.org/z/vrKc9877v

(click/tap to expand, this is the stripped minimal example that I could come up with that reproduces the problem)
#include <vector>
#include <unordered_map>
#include <variant>
#include <type_traits>
#include <cstdint> // for uint8_t

namespace detail {
  template<typename Allocator, typename T>
  using ReboundAllocator = typename std::allocator_traits<Allocator>::template rebind_alloc<T>;
  template<typename T>
  using RemoveCVRef = std::remove_cv_t<std::remove_reference_t<T>>;
}

template<typename Allocator>
struct BasicValue {
  using Null = std::monostate;
  using Object = std::unordered_map<int, BasicValue,
    std::hash<int>, std::equal_to<int>,
    detail::ReboundAllocator<Allocator, std::pair<const int, BasicValue>>>;
  using Variant = std::variant<Null, Object>;

  BasicValue() = default;
  template<typename T,
    std::enable_if_t<!std::is_same_v<detail::RemoveCVRef<T>, BasicValue> &&
    std::is_convertible_v<T&&, Variant>, int> = 0>
  BasicValue(T &&v) noexcept(std::is_nothrow_constructible_v<Variant, T&&>) : m_data{ std::forward<T>(v) } {}

private:
  struct Workaround final {
    Workaround() noexcept { ::new(&storage) Variant{}; }
    template<typename... T>
    Workaround(T&&... v) noexcept(std::is_nothrow_constructible_v<Variant, T&&...>) {
      ::new(&storage) Variant{ std::forward<T>(v)... };
    }
    Workaround(const Workaround &other) { ::new(&storage) Variant{ other.operator const Variant & () }; }
    ~Workaround() {
      static_assert(sizeof(DummyVariant) == sizeof(Variant));
      static_assert(alignof(DummyVariant) == alignof(Variant));
      operator Variant&().~Variant();
    }
    auto &operator=(const Workaround &other) { return operator Variant & () = other.operator const Variant & (); }
    operator const Variant&() const& noexcept { return *std::launder(reinterpret_cast<const Variant*>(&storage)); }
    operator Variant&() & noexcept { return *std::launder(reinterpret_cast<Variant*>(&storage)); }

  private:
    using DummyUnorderedMap = std::unordered_map<int, int,
      std::hash<int>, std::equal_to<int>,
      detail::ReboundAllocator<Allocator, std::pair<const int, int>>>;
    using DummyVariant = std::variant<Null, DummyUnorderedMap>;
    alignas(DummyVariant) uint8_t storage[sizeof(DummyVariant)]{};
  } m_data;
};

// for some reason clang needs this
//static_assert(std::is_copy_constructible_v<BasicValue<std::allocator<char>>>);

using Value = BasicValue<std::allocator<char>>;


struct ShouldBeIrrelevant {
  // commenting any of the following lines gives:
  // "error: incomplete type 'std::unordered_map<...>"
  Value value;
  std::vector<int> bar;
};


int main() {
  Value v;
  const auto c = std::unordered_map<int, Value>{}; 

  //error: call to implicitly-deleted copy constructor of 'std::pair<const int, BasicValue<std::allocator<char>>>'
  v = c;
}
giving error
error: call to implicitly-deleted copy constructor of 'std::pair<const int, BasicValue<std::allocator<char>>>'

while other compilers have no problems with this code, namely GCC and MSVC.

Problem

C++17 mode

Something breaks in clang (practically all versions supporting C++17 "enough", i.e. >7.0.1) with this code when it is used with libstdc++ version <12 (in particular versions 9.2..11.4, earlier versions do not support C++17 "enough").

If you remove the part

struct ShouldBeIrrelevant {
  Value value;
  std::vector<int> bar;
};

or one or both of the members clang fails with error:

error: incomplete type 'std::unordered_map<int, BasicValue<std::allocator<char>>>' used in type trait expression

though it seems completely irrelevant to the code that causes the error:

Value v;
const auto c = std::unordered_map<int, Value>{};
v = c; // error

Later standard modes

With e.g. C++20 or C++23, the error is spelled differently but is practically the same:

error: no matching function for call to 'construct_at'

Workaround

If I add a static_assert

static_assert(std::is_copy_constructible_v<BasicValue<std::allocator<char>>>);

everythng starts to work as expected, and the struct ShouldBeIrrelevant snippet becomes truly irrelevant, i.e. removing it completely or removing any or both of the members has no effect.

Context

In libstdc++ versions <12 std::unordered_map does not support incomplete types, we can work around that by using byte array storage, placement ::new() to construct an object in that storage, and reinterpret_cast to turn it to a pointer to desired type inside implementation where the type is complete.

We guess that alignment and size of some complete std::unordered_map instantiation is the same as the one with currently incomplete type, and static_assert that we guessed correctly in the destructor.

Metadata

Metadata

Assignees

No one assigned

    Labels

    clang:frontendLanguage frontend issues, e.g. anything involving "Sema"Language frontend issues, e.g. anything involving "Sema"libstdc++GNU libstdc++ C++ standard libraryGNU libstdc++ C++ standard libraryneeds-reductionLarge reproducer that should be reduced into a simpler formLarge reproducer that should be reduced into a simpler form

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.