c++c++23

non-exported variable in module interface unit has multiple addresses


I have a variable exception_transformers in coroutine.cpp. If this variable is in an anonymous namespace, then the output shows that register_exception_handler() accesses it at an address 0x18 bytes beyond where every other function accesses it. If exception_transformers is removed from the anonymous namespace, then it is accessed at the same address by all functions.

Is there some interaction between the C++ module and the anonymous namespace that is causing this? Is there some other cause of this problem?


In n4928 (C++23-ish), 9.8.2.2 [namespace.unnamed]

...all occurrences of unique in a translation unit are replaced by the same identifier, and this identifier differs from all other identifiers in the translation unit.

To me, this indicates that both anonymous namespaces behave as if they have the same name, so having the declaration and definition of transform_exception() in two different anonymous namespaces in the same file shouldn't be causing an issue.


https://godbolt.org/z/59EWe5G3b

For clang 19:

module;

#include <coroutine>
#include <expected>
#include <functional>
#include <iostream>
#include <ranges>
#include <stdexcept>
#include <system_error>
#include <type_traits>
#include <vector>

export module mycoroutine;
export import :stdexception;

export {
  namespace exco {
  template <typename T> using result_t = std::expected<T, std::error_code>;
  }
}

namespace {
auto transform_exception() -> exco::result_t<void>;
std::vector<std::function<exco::result_t<void>()>> exception_transformers{transform_exception};
}

export {
  namespace exco {
  auto register_exception_handler(std::function<exco::result_t<void>()> func) {
    std::cout << "register_exception_handler()" << &exception_transformers
              << ' ' << exception_transformers.size() << '\n';
    exception_transformers.emplace_back(std::move(func));
    std::cout << "register_exception_handler()" << &exception_transformers
              << ' ' << exception_transformers.size() << ' '
              << sizeof(exception_transformers) << '\n';
  }
  inline auto unerr(auto const t) {
    return std::unexpected{make_error_code(t)};
  }

  template <typename T> struct expected_wrapper {
    // Initialize as errno 0 so there are no restrictions on T
    // caused by initializing this
    exco::result_t<T> m_result{exco::unerr(static_cast<std::errc>(0))};
    expected_wrapper<T> *&m_ptr_to_this;
    expected_wrapper(expected_wrapper<T> *&ptr_to_this)
        : m_ptr_to_this{ptr_to_this} {
      m_ptr_to_this = this;
    }
    operator result_t<T>() { return std::move(m_result); };
  };
  } // namespace exco

  namespace std {
  template <typename T, typename... Args>
  struct coroutine_traits<exco::result_t<T>, Args...> {
    class promise_type;
    template <typename T1> struct awaiter_type {
      exco::result_t<T1> &m_result;
      explicit awaiter_type(exco::result_t<T1> &result) noexcept
          : m_result{result} {}
      auto await_ready() { return m_result.has_value(); }
      auto await_suspend(std::coroutine_handle<promise_type> h) {
        // This should only happen when await_ready() returns false,
        // which means that has_value() returned false.
        h.destroy();
      }
      auto await_resume() {
        // This should only happen when await_ready() returns true,
        // which means that has_value() returned true.
        return m_result.value();
      }
    };

    class promise_type {
      exco::expected_wrapper<T> *m_ptr_to_wrapper;

    public:
      auto initial_suspend() noexcept -> std::suspend_never { return {}; }
      auto final_suspend() noexcept -> std::suspend_never { return {}; }
      auto return_value(std::error_code ec) {
        m_ptr_to_wrapper->m_result = std::unexpected{ec};
      }
      auto return_value(auto &&t) { m_ptr_to_wrapper->m_result = std::move(t); }
      auto get_return_object() {
        return exco::expected_wrapper<T>{m_ptr_to_wrapper};
      }
      auto unhandled_exception() {
        for (auto &f : exception_transformers | std::views::reverse) {
          try {
            auto result = f();
            if (!result.has_value()) {
              m_ptr_to_wrapper->m_result = std::unexpected{result.error()};
              return;
            }
          } catch (...) {
          }
        }
        m_ptr_to_wrapper->m_result = exco::unerr(exco::stdexception::unknown);
      }
      template <typename T1> auto await_transform(exco::result_t<T1> value) {
        m_ptr_to_wrapper->m_result = std::move(value);
        return awaiter_type<T1>{m_ptr_to_wrapper->m_result};
      }
    };
  };
  } // namespace std
}

namespace {
auto transform_exception() -> exco::result_t<void> {
  std::cout << "transform_exception()" << &exception_transformers << ' '
            << exception_transformers.size() << '\n';
  try {
    throw;
  } catch (std::exception const &) {
    return exco::unerr(exco::stdexception::exception);
  }
}
}

Solution

  • Long answer short, accessing a TU-local (Translation Unit local) variable from an exported function in a Module Interface Unit is ill-formed.


    I reported this to Clang as this bug report. https://github.com/llvm/llvm-project/issues/130396

    It was marked as a duplicate of this bug report. https://github.com/llvm/llvm-project/issues/112294

    Clang does not currently (19.1.0) report a warning or error for this ill-formed construction. It is expected that it will be implemented in the Clang 21 timeframe.