bloomberg/clang-p2996

Non-type template argument `members_of(^...).size()` is not a constant expression

Closed this issue · 4 comments

Describe the bug
Inspired by this issue, I implemented function get_enum_members(). The code still worked yesterday. Nevertheless, it refused to compile sometime today.

Code

template <class E>
concept enumeration = std::is_enum_v<E>;

template <enumeration E>
using enum_member = std::pair<E, std::string_view>;  // value: name

template <enumeration E>
consteval auto get_enum_members() {
    auto enum_members_info = members_of(^E);
    std::array<enum_member<E>, members_of(^E).size()> enum_members;
    for (std::size_t i{}; i < enum_members_info.size(); ++i) {
        enum_members[i] = {value_of<E>(enum_members_info[i]),
                           name_of(enum_members_info[i])};
    }
    return enum_members;
}

Error message

<source>:17:32: error: non-type template argument is not a constant expression
   17 |     std::array<enum_member<E>, members_of(^E).size()> enum_members;
      |                                ^~~~~~~~~~~~~~~~~~~~~
<source>:46:35: note: in instantiation of function template specialization 'get_enum_members<std::filesystem::copy_options>' requested here
   46 |     constexpr auto enum_members = get_enum_members<E>();
      |                                   ^
/opt/compiler-explorer/clang-bb-p2996-trunk-20240514/bin/../include/c++/v1/experimental/meta:354:12: note: subexpression not valid in a constant expression
  354 |     return __metafunction(detail::__metafn_get_begin_member_decl_of,
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  355 |                           reflectedEntity, ^sentinel);
      |                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-bb-p2996-trunk-20240514/bin/../include/c++/v1/experimental/meta:273:21: note: in call to 'm_front.operator()(^(type))'
  273 |     , m_currInfoItr{m_front(reflectedEntity)}
      |                     ^~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-bb-p2996-trunk-20240514/bin/../include/c++/v1/experimental/meta:321:7: note: in call to 'iterator(^(type), {})'
  321 |     : m_first(reflection, pred) , m_last(pred)
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-bb-p2996-trunk-20240514/bin/../include/c++/v1/experimental/meta:519:14: note: in call to 'range(^(type), {})'
  519 |   auto rng = range{r, pred};
      |              ^~~~~~~~~~~~~~
/opt/compiler-explorer/clang-bb-p2996-trunk-20240514/bin/../include/c++/v1/experimental/meta:531:10: note: in call to 'members_of<std::meta::__range_of_infos::always_true_fn>(^(type), {})'
  531 |   return members_of(r, __range_of_infos::always_true);
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:17:32: note: in call to 'members_of(^(type))'
   17 |     std::array<enum_member<E>, members_of(^E).size()> enum_members;
      |                                ^~~~~~~~~~~~~~
<source>:27:42: error: no matching function for call to 'get_enum_members'
   27 |     constexpr static auto enum_members = get_enum_members<E>();
      |                                          ^~~~~~~~~~~~~~~~~~~
<source>:55:27: note: in instantiation of function template specialization 'get_enum_member_name_consteval<std::filesystem::copy_options>' requested here
   55 |     constexpr auto name = get_enum_member_name_consteval(E::overwrite_existing);
      |                           ^
<source>:15:16: note: candidate template ignored: substitution failure [with E = std::filesystem::copy_options]
   15 | consteval auto get_enum_members() {
      |                ^
<source>:55:27: error: constexpr variable 'name' must be initialized by a constant expression
   55 |     constexpr auto name = get_enum_member_name_consteval(E::overwrite_existing);
      |                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:39:9: error: no matching function for call to 'get_enum_members'
   39 |         get_enum_members<E>() | std::ranges::to<std::unordered_map>();
      |         ^~~~~~~~~~~~~~~~~~~
<source>:58:18: note: in instantiation of function template specialization 'get_enum_member_name_runtime<std::filesystem::copy_options>' requested here
   58 |                  get_enum_member_name_runtime(E::overwrite_existing));
      |                  ^
<source>:15:16: note: candidate template ignored: substitution failure [with E = std::filesystem::copy_options]
   15 | consteval auto get_enum_members() {
      |                ^
4 errors generated.
Compiler returned: 1

Possible causes
The most recent commit adds an additional argument to function __metafunction().

libcxx/include/experimental/meta
@@ -783,7 +783,7 @@ consteval auto is_special_member(info r) -> bool {
-  return __metafunction(detail::__metafn_reflect_value, r);
+  return __metafunction(detail::__metafn_reflect_value, ^Ty, r);

It seems that only enumerations are affected. The code still works for structures and classes.

Sorry, I forgot function enumerators_of() and that works. So, it was a bug that function members_of() used to work for enumerations, which has been fixed?

Hey @Shuangcheng-Ni ! Thanks for opening the issue, and good find.

As you've already figured out, enumerators_of(^Enum) should be used, and it was a bug for members_of(^Enum) to be allowed in the first place. It seems that this change is what (quite accidentally) fixed the bug.

What was previously happening was that reflecting on some typename T within a template was accidentally picking up a "SubstTemplateTypeParmType" type-node, rather than the "replacement type" (i.e., the template argument that it was instantiated with). I'm surprised that didn't cause more issues in the first place 😅 With the above change, all code that accesses the type reflected by a std::meta::info will receive a type for which the SubstTemplateTypeParmType layer has already been "unwrapped".

Let me know if there's anything else I can help with, or if we can mark this one as closed :)

Thanks for the clarification and congratulations on the bug fix! I have no other questions at the moment, so I'm going to close this issue as completed.