Skip to content

[C++20] [Module] lambda in initializer of non-inline variable in named modules is internal to the TU incorrectly #110146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Tracked by #112295
stripe2933 opened this issue Sep 26, 2024 · 11 comments
Assignees
Labels
ABI Application Binary Interface clang:modules C++20 modules and Clang Header Modules

Comments

@stripe2933
Copy link
Contributor

Following code works with Clang 19 + libc++.

main.cpp

import std;

#define FWD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)

namespace ranges::views {
    constexpr auto deref = std::views::transform([](auto &&x) { return *FWD(x); });
}

int main() {
    auto opts = { std::optional { 1 }, std::optional { 2 } };
    std::println("{}", opts | ranges::views::deref); // [1, 2]
}

But this does not:

main.cpp

import std;
import ranges;

int main() {
    auto opts = { std::optional { 1 }, std::optional { 2 } };
    std::println("{}", opts | ranges::views::deref);
}

ranges.cppm

export module ranges;

import std;

#define FWD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)

namespace ranges::views {
    export constexpr auto deref = std::views::transform([](auto &&x) { return *FWD(x); });
}
====================[ Build | hello | macos-clang-19 ]==========================
/opt/homebrew/Cellar/cmake/3.30.0/bin/cmake --build /Users/stripe2933/Desktop/hello/build/macos-clang-19 --target hello -j 6
[3/4] Building CXX object CMakeFiles/hello.dir/main.cpp.o
FAILED: CMakeFiles/hello.dir/main.cpp.o 
/opt/homebrew/opt/llvm@19/bin/clang++   -nostdinc++ -nostdlib++ -isystem /opt/homebrew/opt/llvm@19/include/c++/v1 -fexperimental-modules-reduced-bmi -std=gnu++23 -arch arm64 -isysroot /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.0.sdk -fcolor-diagnostics -MD -MT CMakeFiles/hello.dir/main.cpp.o -MF CMakeFiles/hello.dir/main.cpp.o.d @CMakeFiles/hello.dir/main.cpp.o.modmap -o CMakeFiles/hello.dir/main.cpp.o -c /Users/stripe2933/Desktop/hello/main.cpp
/Users/stripe2933/Desktop/hello/main.cpp:6:29: error: invalid operands to binary expression ('std::initializer_list<std::optional<int>>' (aka 'initializer_list<optional<int>>') and 'const __range_adaptor_closure_t<__bind_back_t<__fn, tuple<(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)>>>' (aka 'const std::__1::ranges::__range_adaptor_closure_t<std::__bind_back_t<std::__1::ranges::views::__transform::__fn, std::__1::tuple<ranges::views::(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)>>>'))
    6 |     std::println("{}", opts | ranges::views::deref);
      |                        ~~~~ ^ ~~~~~~~~~~~~~~~~~~~~
/opt/homebrew/opt/llvm@19/include/c++/v1/cstddef:73:45: note: candidate function not viable: no known conversion from 'std::initializer_list<std::optional<int>>' (aka 'initializer_list<optional<int>>') to 'byte' for 1st argument
   73 | _LIBCPP_HIDE_FROM_ABI inline constexpr byte operator|(byte __lhs, byte __rhs) noexcept {
      |                                             ^         ~~~~~~~~~~
/opt/homebrew/opt/llvm@19/include/c++/v1/__charconv/chars_format.h:34:53: note: candidate function not viable: no known conversion from 'std::initializer_list<std::optional<int>>' (aka 'initializer_list<optional<int>>') to 'chars_format' for 1st argument
   34 | inline _LIBCPP_HIDE_FROM_ABI constexpr chars_format operator|(chars_format __x, chars_format __y) {
      |                                                     ^         ~~~~~~~~~~~~~~~~
/opt/homebrew/opt/llvm@19/include/c++/v1/future:431:47: note: candidate function not viable: no known conversion from 'std::initializer_list<std::optional<int>>' (aka 'initializer_list<optional<int>>') to 'launch' for 1st argument
  431 | inline _LIBCPP_HIDE_FROM_ABI constexpr launch operator|(launch __x, launch __y) {
      |                                               ^         ~~~~~~~~~~
/opt/homebrew/opt/llvm@19/include/c++/v1/bitset:932:1: note: candidate template ignored: could not match 'bitset' against 'std::initializer_list'
  932 | operator|(const bitset<_Size>& __x, const bitset<_Size>& __y) _NOEXCEPT {
      | ^
/opt/homebrew/opt/llvm@19/include/c++/v1/__ranges/range_adaptor.h:71:1: note: candidate template ignored: constraints not satisfied [with _Range = std::initializer_list<std::optional<int>> &, _Closure = const __range_adaptor_closure_t<__bind_back_t<__fn, tuple<(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)>>> &]
   71 | operator|(_Range&& __range, _Closure&& __closure) noexcept(is_nothrow_invocable_v<_Closure, _Range>) {
      | ^
/opt/homebrew/opt/llvm@19/include/c++/v1/__ranges/range_adaptor.h:69:12: note: because 'invocable<const std::__1::ranges::__range_adaptor_closure_t<std::__bind_back_t<std::__1::ranges::views::__transform::__fn, std::__1::tuple<ranges::views::(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)> > > &, std::initializer_list<std::__1::optional<int> > &>' evaluated to false
   69 |   requires invocable<_Closure, _Range>
      |            ^
/opt/homebrew/opt/llvm@19/include/c++/v1/__concepts/invocable.h:28:3: note: because 'std::invoke(std::forward<_Fn>(__fn), std::forward<_Args>(__args)...)' would be invalid: no matching function for call to 'invoke'
   28 |   std::invoke(std::forward<_Fn>(__fn), std::forward<_Args>(__args)...); // not required to be equality preserving
      |   ^
/opt/homebrew/opt/llvm@19/include/c++/v1/__ranges/range_adaptor.h:77:52: note: candidate template ignored: constraints not satisfied [with _Closure = std::initializer_list<std::optional<int>> &, _OtherClosure = const __range_adaptor_closure_t<__bind_back_t<__fn, tuple<(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)>>> &]
   77 | [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator|(_Closure&& __c1, _OtherClosure&& __c2) noexcept(
      |                                                    ^
/opt/homebrew/opt/llvm@19/include/c++/v1/__ranges/range_adaptor.h:75:11: note: because 'std::initializer_list<std::__1::optional<int>> &' does not satisfy '_RangeAdaptorClosure'
   75 | template <_RangeAdaptorClosure _Closure, _RangeAdaptorClosure _OtherClosure>
      |           ^
/opt/homebrew/opt/llvm@19/include/c++/v1/__ranges/range_adaptor.h:62:32: note: because '!ranges::range<remove_cvref_t<initializer_list<optional<int> > &> >' evaluated to false
   62 | concept _RangeAdaptorClosure = !ranges::range<remove_cvref_t<_Tp>> && requires {
      |                                ^
/opt/homebrew/opt/llvm@19/include/c++/v1/valarray:2858:1: note: candidate template ignored: requirement '__is_val_expr<std::initializer_list<std::__1::optional<int>>>::value' was not satisfied [with _Expr1 = std::initializer_list<std::optional<int>>, _Expr2 = __range_adaptor_closure_t<__bind_back_t<__fn, tuple<(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)>>>]
 2858 | operator|(const _Expr1& __x, const _Expr2& __y) {
      | ^
/opt/homebrew/opt/llvm@19/include/c++/v1/valarray:2867:1: note: candidate template ignored: requirement '__is_val_expr<std::initializer_list<std::__1::optional<int>>>::value' was not satisfied [with _Expr = std::initializer_list<std::optional<int>>]
 2867 | operator|(const _Expr& __x, const typename _Expr::value_type& __y) {
      | ^
/opt/homebrew/opt/llvm@19/include/c++/v1/valarray:2876:1: note: candidate template ignored: requirement '__is_val_expr<std::__1::ranges::__range_adaptor_closure_t<std::__bind_back_t<std::__1::ranges::views::__transform::__fn, std::__1::tuple<ranges::views::(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)>>>>::value' was not satisfied [with _Expr = __range_adaptor_closure_t<__bind_back_t<__fn, tuple<(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)>>>]
 2876 | operator|(const typename _Expr::value_type& __x, const _Expr& __y) {
      | ^
1 error generated.
ninja: build stopped: subcommand failed.

Even if I removed -fexperimental-modules-reduced-bmi argument, it still throws errors.

@EugeneZelenko EugeneZelenko added clang:modules C++20 modules and Clang Header Modules and removed new issue labels Sep 26, 2024
@llvmbot
Copy link
Member

llvmbot commented Sep 26, 2024

@llvm/issue-subscribers-clang-modules

Author: LEE KYOUNGHEON (stripe2933)

Following code works with Clang 19 + libc++.

main.cpp

import std;

#define FWD(...) static_cast&lt;decltype(__VA_ARGS__)&amp;&amp;&gt;(__VA_ARGS__)

namespace ranges::views {
    constexpr auto deref = std::views::transform([](auto &amp;&amp;x) { return *FWD(x); });
}

int main() {
    auto opts = { std::optional { 1 }, std::optional { 2 } };
    std::println("{}", opts | ranges::views::deref); // [1, 2]
}

But this does not:

main.cpp

import std;
import ranges;

int main() {
    auto opts = { std::optional { 1 }, std::optional { 2 } };
    std::println("{}", opts | ranges::views::deref);
}

ranges.cppm

export module ranges;

import std;

#define FWD(...) static_cast&lt;decltype(__VA_ARGS__)&amp;&amp;&gt;(__VA_ARGS__)

namespace ranges::views {
    export constexpr auto deref = std::views::transform([](auto &amp;&amp;x) { return *FWD(x); });
}
====================[ Build | hello | macos-clang-19 ]==========================
/opt/homebrew/Cellar/cmake/3.30.0/bin/cmake --build /Users/stripe2933/Desktop/hello/build/macos-clang-19 --target hello -j 6
[3/4] Building CXX object CMakeFiles/hello.dir/main.cpp.o
FAILED: CMakeFiles/hello.dir/main.cpp.o 
/opt/homebrew/opt/llvm@<!-- -->19/bin/clang++   -nostdinc++ -nostdlib++ -isystem /opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1 -fexperimental-modules-reduced-bmi -std=gnu++23 -arch arm64 -isysroot /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.0.sdk -fcolor-diagnostics -MD -MT CMakeFiles/hello.dir/main.cpp.o -MF CMakeFiles/hello.dir/main.cpp.o.d @<!-- -->CMakeFiles/hello.dir/main.cpp.o.modmap -o CMakeFiles/hello.dir/main.cpp.o -c /Users/stripe2933/Desktop/hello/main.cpp
/Users/stripe2933/Desktop/hello/main.cpp:6:29: error: invalid operands to binary expression ('std::initializer_list&lt;std::optional&lt;int&gt;&gt;' (aka 'initializer_list&lt;optional&lt;int&gt;&gt;') and 'const __range_adaptor_closure_t&lt;__bind_back_t&lt;__fn, tuple&lt;(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)&gt;&gt;&gt;' (aka 'const std::__1::ranges::__range_adaptor_closure_t&lt;std::__bind_back_t&lt;std::__1::ranges::views::__transform::__fn, std::__1::tuple&lt;ranges::views::(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)&gt;&gt;&gt;'))
    6 |     std::println("{}", opts | ranges::views::deref);
      |                        ~~~~ ^ ~~~~~~~~~~~~~~~~~~~~
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/cstddef:73:45: note: candidate function not viable: no known conversion from 'std::initializer_list&lt;std::optional&lt;int&gt;&gt;' (aka 'initializer_list&lt;optional&lt;int&gt;&gt;') to 'byte' for 1st argument
   73 | _LIBCPP_HIDE_FROM_ABI inline constexpr byte operator|(byte __lhs, byte __rhs) noexcept {
      |                                             ^         ~~~~~~~~~~
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/__charconv/chars_format.h:34:53: note: candidate function not viable: no known conversion from 'std::initializer_list&lt;std::optional&lt;int&gt;&gt;' (aka 'initializer_list&lt;optional&lt;int&gt;&gt;') to 'chars_format' for 1st argument
   34 | inline _LIBCPP_HIDE_FROM_ABI constexpr chars_format operator|(chars_format __x, chars_format __y) {
      |                                                     ^         ~~~~~~~~~~~~~~~~
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/future:431:47: note: candidate function not viable: no known conversion from 'std::initializer_list&lt;std::optional&lt;int&gt;&gt;' (aka 'initializer_list&lt;optional&lt;int&gt;&gt;') to 'launch' for 1st argument
  431 | inline _LIBCPP_HIDE_FROM_ABI constexpr launch operator|(launch __x, launch __y) {
      |                                               ^         ~~~~~~~~~~
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/bitset:932:1: note: candidate template ignored: could not match 'bitset' against 'std::initializer_list'
  932 | operator|(const bitset&lt;_Size&gt;&amp; __x, const bitset&lt;_Size&gt;&amp; __y) _NOEXCEPT {
      | ^
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/__ranges/range_adaptor.h:71:1: note: candidate template ignored: constraints not satisfied [with _Range = std::initializer_list&lt;std::optional&lt;int&gt;&gt; &amp;, _Closure = const __range_adaptor_closure_t&lt;__bind_back_t&lt;__fn, tuple&lt;(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)&gt;&gt;&gt; &amp;]
   71 | operator|(_Range&amp;&amp; __range, _Closure&amp;&amp; __closure) noexcept(is_nothrow_invocable_v&lt;_Closure, _Range&gt;) {
      | ^
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/__ranges/range_adaptor.h:69:12: note: because 'invocable&lt;const std::__1::ranges::__range_adaptor_closure_t&lt;std::__bind_back_t&lt;std::__1::ranges::views::__transform::__fn, std::__1::tuple&lt;ranges::views::(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)&gt; &gt; &gt; &amp;, std::initializer_list&lt;std::__1::optional&lt;int&gt; &gt; &amp;&gt;' evaluated to false
   69 |   requires invocable&lt;_Closure, _Range&gt;
      |            ^
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/__concepts/invocable.h:28:3: note: because 'std::invoke(std::forward&lt;_Fn&gt;(__fn), std::forward&lt;_Args&gt;(__args)...)' would be invalid: no matching function for call to 'invoke'
   28 |   std::invoke(std::forward&lt;_Fn&gt;(__fn), std::forward&lt;_Args&gt;(__args)...); // not required to be equality preserving
      |   ^
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/__ranges/range_adaptor.h:77:52: note: candidate template ignored: constraints not satisfied [with _Closure = std::initializer_list&lt;std::optional&lt;int&gt;&gt; &amp;, _OtherClosure = const __range_adaptor_closure_t&lt;__bind_back_t&lt;__fn, tuple&lt;(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)&gt;&gt;&gt; &amp;]
   77 | [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator|(_Closure&amp;&amp; __c1, _OtherClosure&amp;&amp; __c2) noexcept(
      |                                                    ^
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/__ranges/range_adaptor.h:75:11: note: because 'std::initializer_list&lt;std::__1::optional&lt;int&gt;&gt; &amp;' does not satisfy '_RangeAdaptorClosure'
   75 | template &lt;_RangeAdaptorClosure _Closure, _RangeAdaptorClosure _OtherClosure&gt;
      |           ^
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/__ranges/range_adaptor.h:62:32: note: because '!ranges::range&lt;remove_cvref_t&lt;initializer_list&lt;optional&lt;int&gt; &gt; &amp;&gt; &gt;' evaluated to false
   62 | concept _RangeAdaptorClosure = !ranges::range&lt;remove_cvref_t&lt;_Tp&gt;&gt; &amp;&amp; requires {
      |                                ^
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/valarray:2858:1: note: candidate template ignored: requirement '__is_val_expr&lt;std::initializer_list&lt;std::__1::optional&lt;int&gt;&gt;&gt;::value' was not satisfied [with _Expr1 = std::initializer_list&lt;std::optional&lt;int&gt;&gt;, _Expr2 = __range_adaptor_closure_t&lt;__bind_back_t&lt;__fn, tuple&lt;(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)&gt;&gt;&gt;]
 2858 | operator|(const _Expr1&amp; __x, const _Expr2&amp; __y) {
      | ^
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/valarray:2867:1: note: candidate template ignored: requirement '__is_val_expr&lt;std::initializer_list&lt;std::__1::optional&lt;int&gt;&gt;&gt;::value' was not satisfied [with _Expr = std::initializer_list&lt;std::optional&lt;int&gt;&gt;]
 2867 | operator|(const _Expr&amp; __x, const typename _Expr::value_type&amp; __y) {
      | ^
/opt/homebrew/opt/llvm@<!-- -->19/include/c++/v1/valarray:2876:1: note: candidate template ignored: requirement '__is_val_expr&lt;std::__1::ranges::__range_adaptor_closure_t&lt;std::__bind_back_t&lt;std::__1::ranges::views::__transform::__fn, std::__1::tuple&lt;ranges::views::(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)&gt;&gt;&gt;&gt;::value' was not satisfied [with _Expr = __range_adaptor_closure_t&lt;__bind_back_t&lt;__fn, tuple&lt;(lambda at /Users/stripe2933/Desktop/hello/ranges.cppm:8:57)&gt;&gt;&gt;]
 2876 | operator|(const typename _Expr::value_type&amp; __x, const _Expr&amp; __y) {
      | ^
1 error generated.
ninja: build stopped: subcommand failed.

Even if I removed -fexperimental-modules-reduced-bmi argument, it still throws errors.

@ChuanqiXu9 ChuanqiXu9 self-assigned this Sep 27, 2024
@ChuanqiXu9
Copy link
Member

ChuanqiXu9 commented Sep 27, 2024

I haven't figured it out completely. But it shows we can workaround it by:

export module ranges;

import std;

#define FWD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)

namespace ranges::views {
+    export  inline constexpr auto deref = std::views::transform([](auto &&x) { return *FWD(x); });
-    export constexpr auto deref = std::views::transform([](auto &&x) { return *FWD(x); });
}

@ChuanqiXu9
Copy link
Member

Sadly this tends to be an ABI issue. According to https://itanium-cxx-abi.github.io/cxx-abi/abi.html:

5.1.8 Closure Types (Lambdas)
In the following contexts, however, the one-definition rule requires closure types in different translation units to "correspond":

  • ...
  • the initializers of inline or templated variables

So that according to the Itanium C++ ABI spec, we only set lambda numbering for the cases listed above (including the initializers of inline variables, this is why the above workaround works). However, this is not true for modules clearly. The lambda is not unique to the translation units if they are used in initializers of varaibles in modules.

I am not sure if we can change the behavior of the compiler before the above proposed wording get approved in Itanium C++ ABI. Otherwise we can only wait for the action of Itanium C++ group, which may take a longer time.

CC @AaronBallman @erichkeane @cor3ntin @mizvekov for the policy issues.

@ChuanqiXu9 ChuanqiXu9 added the ABI Application Binary Interface label Sep 27, 2024
@ChuanqiXu9 ChuanqiXu9 changed the title C++20 module error: moving the function into module causes the wrong template instantiation [C++20] [Module] lambda in initializer of non-inline variable in named modules is internal to the TU incorrectly Sep 27, 2024
@mizvekov
Copy link
Contributor

The straight conversion of this example using preprocessor includes, without modules, doesn't work as well, so I tend to think this is not a bug, the user should have used inline as you suggested.

Wouldn't this be an ODR violation anyway, and the real problem here is that we miss diagnosing that?

@ChuanqiXu9
Copy link
Member

The straight conversion of this example using preprocessor includes, without modules, doesn't work as well

What's the example? In the single TU form, https://godbolt.org/z/1zre9eMjb, it works well. Do you mean split the definition of deref in other TUs? Then I think it wouldn't work well naturally.

Wouldn't this be an ODR violation anyway, and the real problem here is that we miss diagnosing that?

Then this is why I think this is an ABI issue. If we add a clause to https://itanium-cxx-abi.github.io/cxx-abi/abi.html#closure-types:

In the following contexts, however, the one-definition rule requires closure types in different translation units to "correspond":

  • ...
  • attached to named modules

it wouldn't be an ODR violation.

@kamrann
Copy link

kamrann commented Sep 30, 2024

I don't see how it relates to ODR. In the given modules case, there is only a single definition, and the entity has module linkage.

Anyway I think this is something more fundamental. It can be reduced to:

// mod.cppm

export module mod;

namespace mod {
    export constexpr auto lambda = [](auto &&x) { return x; };
}
// main.cpp

import std;
import mod;

constexpr auto local_lambda = [](auto &&x) { return x; };

int main() {
    auto vals = std::array{ 1, 2 };
    std::views::transform(local_lambda)(vals); // ok
    std::views::transform(mod::lambda)(vals); // error
}

And indeed further, to:

// main.cpp

import mod;

constexpr auto local_lambda = [](auto &&x) { return x; };

auto a = local_lambda;
auto b = mod::lambda; // error

And now I see that it's the same issue as #59513, and also that you already realized that, @ChuanqiXu9. Anyway I'll leave this reduction here just in case it's useful.

@mizvekov
Copy link
Contributor

mizvekov commented Oct 1, 2024

The straight conversion example I mean something like:

// --- A.h
constexpr auto lambda = []{};

// --- B.cc
#include "A.h"

// --- C.cc
#include "A.h"

Here we have multiple definitions of non-inline lambda variable, so this can't work.
Further, their declarations don't match because the lambdas don't correspond.

I believe the modules example has the same issue.

It's exporting the definition of a non-inline variable, which means that if another module imports it, then we are going to end up with multiple TUs defining the same non-inline variable. And non-withstanding the fact that the lambdas still don't correspond.

It seems you want to fix the issue by having all variables behave as inline variables? Or maybe just those exported by modules? Should that extend to functions as well?

Anyway, that seems like a language evolution issue rather than an ABI issue.

@kamrann
Copy link

kamrann commented Oct 1, 2024

Here we have multiple definitions of non-inline lambda variable, so this can't work. Further, their declarations don't match because the lambdas don't correspond.

I believe the modules example has the same issue.

That issue is an artefact of the textual inclusion model. Modules don't work this way, on the language level at any rate. There is only one definition, in the module, and all that gets exported is the name. Importing TUs simply have access to that name, they don't redefine the entity.

@mizvekov
Copy link
Contributor

mizvekov commented Oct 1, 2024

I don't see how it relates to ODR. In the given modules case, there is only a single definition, and the entity has module linkage.

In the given example, the entity is exported, so how would it have module linkage?

That issue is an artefact of the textual inclusion model. Modules don't work this way, on the language level at any rate. There is only one definition, in the module, and all that gets exported is the name. Importing TUs simply have access to that name, they don't redefine the entity.

Exporting just the declaration wouldn't work for a constexpr variable, a definition is needed at the point of use.

@kamrann
Copy link

kamrann commented Oct 1, 2024

In the given example, the entity is exported, so how would it have module linkage?

Sorry yes my mistake, I was mixing up linkage vs reachability as being orthogonal to export. Indeed, it won't have module linkage.

Exporting just the declaration wouldn't work for a constexpr variable, a definition is needed at the point of use.

Right, I guess it starts to get tricky with terminology and the precise meaning of "definition", but I'm referring specifically to the language's idea of a definition. While the compiler for sure needs to include such information into the BMI for use on the importing side, as far as I understand that doesn't constitute a definition of the entity (in the ODR sense) in the importing TU.

@ChuanqiXu9
Copy link
Member

It's exporting the definition of a non-inline variable, which means that if another module imports it, then we are going to end up with multiple TUs defining the same non-inline variable. And non-withstanding the fact that the lambdas still don't correspond.

which means that if another module imports it, then we are going to end up with multiple TUs defining the same non-inline variable.

No, we don't change the behavior of the variable. The behavior of the variable won't be affected by the proposal. What we changes is the behavior of the lambda: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#closure-types

This was defined in the ABI spec and not in the language spec. So I think this is an ABI issue instead of a language evolving issue. And @zygoloid agrees on the direction.

I feel good to help to sent this to EWG (or CWG?) if you still have questions.

llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this issue Mar 13, 2025
…ternal module unit context

Close llvm/llvm-project#59513
Close llvm/llvm-project#110146

As we discussed, this is related to ABI:
itanium-cxx-abi/cxx-abi#186

I was intending to fix this after it gets merged into the ItaniumC++ABI
formally. But it looks like ItaniumC++ABI doesn't update it yet and
there are more issue reports for it.

Luckily Richard had a clear direction guide here though. So I think it
should be good to do this without a formal ItaniumC++ABI wording.

The diff of the patch is slightly larger than it was by a simple
refacoration to simple the control flow a little bit.
frederik-h pushed a commit to frederik-h/llvm-project that referenced this issue Mar 18, 2025
…le unit context

Close llvm#59513
Close llvm#110146

As we discussed, this is related to ABI:
itanium-cxx-abi/cxx-abi#186

I was intending to fix this after it gets merged into the ItaniumC++ABI
formally. But it looks like ItaniumC++ABI doesn't update it yet and
there are more issue reports for it.

Luckily Richard had a clear direction guide here though. So I think it
should be good to do this without a formal ItaniumC++ABI wording.

The diff of the patch is slightly larger than it was by a simple
refacoration to simple the control flow a little bit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ABI Application Binary Interface clang:modules C++20 modules and Clang Header Modules
Projects
None yet
Development

No branches or pull requests

6 participants