wise_enum icon indicating copy to clipboard operation
wise_enum copied to clipboard

wise_enum::size compile error

Open martinfinke opened this issue 5 years ago • 12 comments

I'm trying to use wise_enum::size like this:

class MyClass final {
public:
    WISE_ENUM_MEMBER(Parameter, First, Second)

    array<Foo, wise_enum::size<Parameter>> values;
};

Unfortunately, I'm getting a compiler error on this line saying: "Constexpr variable 'range' must be initialized by a constant expression" and "Undefined function 'wise_enum_detail_array' cannot be used in a constant expression".

wise_enum_detail_array seems to be defined through the WISE_ENUM_IMPL_ADAPT_3 macro, but I couldn't figure out what exactly is going wrong there..

Any help would be much appreciated! 😊 I'm using Xcode Version 10.1 (10B61) with C++14 standard, with wise_enum at the latest master commit.

martinfinke avatar Aug 14 '19 08:08 martinfinke

Can you reproduce this on clang on a linux system? To me it looks like a compiler bug but it's hard for me to be sure without reproducing.

quicknir avatar Aug 27 '19 22:08 quicknir

Unfortunately I don't have a linux system set up (or a VM), so I can't test this..for now I'm just using the old enum trick of adding a NumParameters case at the end.

martinfinke avatar Sep 06 '19 06:09 martinfinke

I realize this thread is over a year old, but I encountered what seems to be the same error, so I thought I'd comment here.

I'm using Xcode 12.1, with C++ 17 (not GNU), with the latest version of Wise Enum as of this writing. This test code:

#include <array>

#include "wise_enum.h"

class Test {
    WISE_ENUM_CLASS_MEMBER(Enum, a, b, c)
    std::array<int, wise_enum::size<Enum>> array;
};

int main(int argc, char * argv[]) {
}

Seems to generate errors similar to those mentioned by @martinfinke, including:

"Constexpr variable 'range' must be initialized by a constant expression"

At wise_enum.h, line 63.

I hope this information will be of use.

jesse-k avatar Nov 05 '20 01:11 jesse-k

The following code:

#include "wise_enum.h"

namespace udaq::udaq
{
    class Test {
        WISE_ENUM_CLASS_MEMBER(Enums, a, b, c)
        std::array<int, wise_enum::size<Enums>> array;
    };
}

Fails with the following when compiled with gcc 10.2.1 x64 on Debian :

/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h: In instantiation of ‘constexpr const std::array<wise_enum::detail::value_and_name<udaq::udaq::Test::Enums>, 3> wise_enum::enumerators<udaq::udaq::Test::Enums>::range’:
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:67:41:   required from ‘constexpr const size_t wise_enum::enumerators<udaq::udaq::Test::Enums>::size’
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:46:   required from ‘constexpr const size_t wise_enum::size<udaq::udaq::Test::Enums>’
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:36:   required from here
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:64:29: error: ‘constexpr std::array<wise_enum::detail::value_and_name<udaq::udaq::Test::Enums>, 3> udaq::udaq::wise_enum_detail_array(wise_enum::detail::Tag<udaq::udaq::Test::Enums>)’ called in a constant expression before its definition is complete
   64 |       wise_enum_detail_array(detail::Tag<T>{});
      |       ~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
make[3]: *** [udaq/CMakeFiles/udaq.dir/build.make:82: udaq/CMakeFiles/udaq.dir/src/test.cpp.o] Error 1

And the following error when compiled with clang 11.0.1-2 on the same box:

/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:63:71: error: constexpr variable 'range' must be initialized by a constant expression
  static constexpr decltype(wise_enum_detail_array(detail::Tag<T>{})) range =
                                                                      ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:67:41: note: in instantiation of static data member 'wise_enum::enumerators<udaq::udaq::Test::Enums>::range' requested here
    static constexpr std::size_t size = range.size();
                                        ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:46: note: in instantiation of static data member 'wise_enum::enumerators<udaq::udaq::Test::Enums>::size' requested here
constexpr std::size_t size = enumerators<T>::size;
                                             ^
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:36: note: in instantiation of variable template specialization 'wise_enum::size<udaq::udaq::Test::Enums>' requested here
        std::array<int, wise_enum::size<Enums>> array;
                                   ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:64:7: note: undefined function 'wise_enum_detail_array' cannot be used in a constant expression
      wise_enum_detail_array(detail::Tag<T>{});
      ^
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:6:9: note: declared here
        WISE_ENUM_CLASS_MEMBER(Enums, a, b, c)
        ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:39:3: note: expanded from macro 'WISE_ENUM_CLASS_MEMBER'
  WISE_ENUM_IMPL(enum class, name, friend, __VA_ARGS__)
  ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum_detail.h:164:3: note: expanded from macro 'WISE_ENUM_IMPL'
  WISE_ENUM_IMPL_2(type, WISE_ENUM_IMPL_ONLY_OR_FIRST(name_storage),           \
  ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum_detail.h:169:3: note: expanded from macro 'WISE_ENUM_IMPL_2'
  WISE_ENUM_IMPL_3(type, name, storage, friendly, num_enums,                   \
  ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum_detail.h:178:3: note: expanded from macro 'WISE_ENUM_IMPL_3'
  WISE_ENUM_IMPL_ADAPT_3(name, friendly, num_enums, loop, __VA_ARGS__)
  ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum_detail.h:195:3: note: expanded from macro 'WISE_ENUM_IMPL_ADAPT_3'
  wise_enum_detail_array(::wise_enum::detail::Tag<name>) {                     \
  ^
In file included from /mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:1:
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:67:34: error: constexpr variable 'size' must be initialized by a constant expression
    static constexpr std::size_t size = range.size();
                                 ^      ~~~~~~~~~~~~
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:46: note: in instantiation of static data member 'wise_enum::enumerators<udaq::udaq::Test::Enums>::size' requested here
constexpr std::size_t size = enumerators<T>::size;
                                             ^
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:36: note: in instantiation of variable template specialization 'wise_enum::size<udaq::udaq::Test::Enums>' requested here
        std::array<int, wise_enum::size<Enums>> array;
                                   ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:67:47: note: initializer of 'range' is not a constant expression
    static constexpr std::size_t size = range.size();
                                              ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:63:71: note: declared here
  static constexpr decltype(wise_enum_detail_array(detail::Tag<T>{})) range =
                                                                      ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:23: error: constexpr variable 'size<udaq::udaq::Test::Enums>' must be initialized by a constant expression
constexpr std::size_t size = enumerators<T>::size;
                      ^      ~~~~~~~~~~~~~~~~~~~~
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:36: note: in instantiation of variable template specialization 'wise_enum::size<udaq::udaq::Test::Enums>' requested here
        std::array<int, wise_enum::size<Enums>> array;
                                   ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:30: note: initializer of 'size' is not a constant expression
constexpr std::size_t size = enumerators<T>::size;
                             ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:67:34: note: declared here
    static constexpr std::size_t size = range.size();
                                 ^
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:25: error: non-type template argument is not a constant expression
        std::array<int, wise_enum::size<Enums>> array;
                        ^~~~~~~~~~~~~~~~~~~~~~
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:25: note: initializer of 'size<udaq::udaq::Test::Enums>' is not a constant expression
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:23: note: declared here
constexpr std::size_t size = enumerators<T>::size;
                      ^
4 errors generated.

samangh avatar Feb 19 '21 12:02 samangh

The following code:

#include "wise_enum.h"

namespace udaq::udaq
{
    class Test {
        WISE_ENUM_CLASS_MEMBER(Enums, a, b, c)
        std::array<int, wise_enum::size<Enums>> array;
    };
}

Fails with the following when compiled with gcc 10.2.1 x64 on Debian :

/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h: In instantiation of ‘constexpr const std::array<wise_enum::detail::value_and_name<udaq::udaq::Test::Enums>, 3> wise_enum::enumerators<udaq::udaq::Test::Enums>::range’:
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:67:41:   required from ‘constexpr const size_t wise_enum::enumerators<udaq::udaq::Test::Enums>::size’
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:46:   required from ‘constexpr const size_t wise_enum::size<udaq::udaq::Test::Enums>’
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:36:   required from here
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:64:29: error: ‘constexpr std::array<wise_enum::detail::value_and_name<udaq::udaq::Test::Enums>, 3> udaq::udaq::wise_enum_detail_array(wise_enum::detail::Tag<udaq::udaq::Test::Enums>)’ called in a constant expression before its definition is complete
   64 |       wise_enum_detail_array(detail::Tag<T>{});
      |       ~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
make[3]: *** [udaq/CMakeFiles/udaq.dir/build.make:82: udaq/CMakeFiles/udaq.dir/src/test.cpp.o] Error 1

And the following error when compiled with clang 11.0.1-2 on the same box:

/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:63:71: error: constexpr variable 'range' must be initialized by a constant expression
  static constexpr decltype(wise_enum_detail_array(detail::Tag<T>{})) range =
                                                                      ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:67:41: note: in instantiation of static data member 'wise_enum::enumerators<udaq::udaq::Test::Enums>::range' requested here
    static constexpr std::size_t size = range.size();
                                        ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:46: note: in instantiation of static data member 'wise_enum::enumerators<udaq::udaq::Test::Enums>::size' requested here
constexpr std::size_t size = enumerators<T>::size;
                                             ^
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:36: note: in instantiation of variable template specialization 'wise_enum::size<udaq::udaq::Test::Enums>' requested here
        std::array<int, wise_enum::size<Enums>> array;
                                   ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:64:7: note: undefined function 'wise_enum_detail_array' cannot be used in a constant expression
      wise_enum_detail_array(detail::Tag<T>{});
      ^
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:6:9: note: declared here
        WISE_ENUM_CLASS_MEMBER(Enums, a, b, c)
        ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:39:3: note: expanded from macro 'WISE_ENUM_CLASS_MEMBER'
  WISE_ENUM_IMPL(enum class, name, friend, __VA_ARGS__)
  ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum_detail.h:164:3: note: expanded from macro 'WISE_ENUM_IMPL'
  WISE_ENUM_IMPL_2(type, WISE_ENUM_IMPL_ONLY_OR_FIRST(name_storage),           \
  ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum_detail.h:169:3: note: expanded from macro 'WISE_ENUM_IMPL_2'
  WISE_ENUM_IMPL_3(type, name, storage, friendly, num_enums,                   \
  ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum_detail.h:178:3: note: expanded from macro 'WISE_ENUM_IMPL_3'
  WISE_ENUM_IMPL_ADAPT_3(name, friendly, num_enums, loop, __VA_ARGS__)
  ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum_detail.h:195:3: note: expanded from macro 'WISE_ENUM_IMPL_ADAPT_3'
  wise_enum_detail_array(::wise_enum::detail::Tag<name>) {                     \
  ^
In file included from /mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:1:
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:67:34: error: constexpr variable 'size' must be initialized by a constant expression
    static constexpr std::size_t size = range.size();
                                 ^      ~~~~~~~~~~~~
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:46: note: in instantiation of static data member 'wise_enum::enumerators<udaq::udaq::Test::Enums>::size' requested here
constexpr std::size_t size = enumerators<T>::size;
                                             ^
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:36: note: in instantiation of variable template specialization 'wise_enum::size<udaq::udaq::Test::Enums>' requested here
        std::array<int, wise_enum::size<Enums>> array;
                                   ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:67:47: note: initializer of 'range' is not a constant expression
    static constexpr std::size_t size = range.size();
                                              ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:63:71: note: declared here
  static constexpr decltype(wise_enum_detail_array(detail::Tag<T>{})) range =
                                                                      ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:23: error: constexpr variable 'size<udaq::udaq::Test::Enums>' must be initialized by a constant expression
constexpr std::size_t size = enumerators<T>::size;
                      ^      ~~~~~~~~~~~~~~~~~~~~
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:36: note: in instantiation of variable template specialization 'wise_enum::size<udaq::udaq::Test::Enums>' requested here
        std::array<int, wise_enum::size<Enums>> array;
                                   ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:30: note: initializer of 'size' is not a constant expression
constexpr std::size_t size = enumerators<T>::size;
                             ^
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:67:34: note: declared here
    static constexpr std::size_t size = range.size();
                                 ^
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:25: error: non-type template argument is not a constant expression
        std::array<int, wise_enum::size<Enums>> array;
                        ^~~~~~~~~~~~~~~~~~~~~~
/mnt/c/Saman/Code/C++/udaq/udaq/src/test.cpp:7:25: note: initializer of 'size<udaq::udaq::Test::Enums>' is not a constant expression
/mnt/c/Saman/Code/C++/udaq/external/wise_enum/wise_enum.h:82:23: note: declared here
constexpr std::size_t size = enumerators<T>::size;
                      ^
4 errors generated.

For what it's worth, although I'm not positive, I think this may be an inherent limitation of the approach the library uses, for reasons discussed here.

jesse-k avatar Feb 19 '21 17:02 jesse-k

Sorry I've let this linger so long; I'm going to take a look at it. It may indeed be a basic limitation of the approach as @jesse-k said. Over the years, I've encountered quite a few nasty edge cases in C++ that have led me to try to avoid nested classes in general, or at least, just give up quickly at the first sign of trouble. I'd probably just recommend defining the enum in a detail namespace and using an alias, an approach that should have very few practical downsides. But I will try to see if this is possible to fix. The first step I think will be coming up with a repro without using any macros to fully understand the issue.

quicknir avatar Feb 25 '21 15:02 quicknir

Okay, I reproduced this with simply:

#include <array>

namespace lib {

template <class T>
struct tag{};

template <class T>
constexpr inline auto range = detail_array(tag<T>{});

template <class T>
constexpr inline auto size = range<T>.size();
}

struct foo {
    enum class bar { FIRST, SECOND };
    constexpr friend std::array<bar, 2> detail_array(lib::tag<bar>) { return { bar::FIRST, bar::SECOND}; }

    std::array<int, lib::size<bar>> x; // doesn't compile
};

int main() {
    constexpr auto y = lib::size<foo::bar>;  // works fine
}

It seems like the compiler is correct to reject this code: "Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.", http://eel.is/c++draft/temp#point-4.sentence-2. Basically, when you try to declare the x member variable, it instantiates lib::size and that template is looked up from before foo is created, so detail_array doesn't exist yet.

So, indeed, it is just a limitation of the approach, although, it seems like almost any generic, free function based approach in C++ will have the exact same problem. At least, I can't imagine how it would avoid it.

The only question I guess is whether to document this behavior, or simply remove support for member enum classes. I'm leaning towards the former. I welcome feedback on this issue from anyone in the thread; I'll close this issue once I've decided and implemented.

quicknir avatar Feb 25 '21 16:02 quicknir

The only question I guess is whether to document this behavior, or simply remove support for member enum classes. I'm leaning towards the former. I welcome feedback on this issue from anyone in the thread; I'll close this issue once I've decided and implemented.

Since I was a participant in the thread, I'll go ahead and offer that documenting it as a limitation seems reasonable (the code wouldn't have to be changed then, and support for member enums can be useful, despite the limitations).

jesse-k avatar Feb 25 '21 18:02 jesse-k

Agreed, I think documenting it as a limitation is the way to go.

samangh avatar Feb 26 '21 13:02 samangh

+1 for documenting, but leaving support for member enums. Thank you for taking the time to look at this @quicknir ! And also thank you @samangh and @jesse-k for narrowing down the problem.

martinfinke avatar Feb 26 '21 14:02 martinfinke

Update on this, it's actually possible to partly workaround this limitation. While fully reflection in the class body is impossible, at least accessing the size is actually possible, because it can be transmitted via the type (at least, that is my understanding, I don't claim to fully understand this). So, if I change the definition of size to:

template <class T>
constexpr inline auto size = std::tuple_size<decltype(detail_array(tag<T>{}))>::value;

Then my example above seems to work. I'll test this within the actual library next, and see if something breaks. Offhand, I don't think there's any major downside here in terms of compile time, may make errors a little more confusing, but it seems like a worthwhile tradeoff.

quicknir avatar Mar 29 '21 15:03 quicknir

Update on this, it's actually possible to partly workaround this limitation.

@quicknir Great work! Thank you 🎉 I can confirm that your solution works on my end as well, both on Clang/Xcode and in Visual Studio 2019. I can have a std::array member with the range/size of a nested enum.

martinfinke avatar Apr 28 '21 08:04 martinfinke