简体   繁体   中英

Why is this a non-constant condition for g++8?

I have this code. This is an array (which is a bit like std :: array ) that I could work with at compile time. Also for-loop at compile time.

#include <utility>
#include <memory>
#include <type_traits>

template<class F,
         std::size_t ... Is>
constexpr void __loop(F&& func, std::index_sequence<Is ...>) noexcept
{
    (func(std::integral_constant<std::size_t, Is>{}), ...);
}

template<std::size_t N,
         typename F>
constexpr void CONSTEXPR_LOOP(F&& func) noexcept
{
    __loop(std::forward<F>(func), std::make_index_sequence<N>());
}

template<typename T, std::size_t Size>
class StaticArray
{
    static_assert(std::disjunction_v<
                        std::is_default_constructible<T>,
                        std::is_nothrow_default_constructible<T>
                    >,
                  "Type must have a trivial constructor.");
public:
    constexpr StaticArray() noexcept;
    template<typename ... Args,
             std::enable_if_t<
                 std::conjunction_v<
                     std::is_same<T, Args>...
                     >
                 > * = nullptr
             >
    constexpr StaticArray(Args && ... list) noexcept;
    constexpr StaticArray(const StaticArray& a) = delete;
    constexpr StaticArray(StaticArray&& a) = delete;
    ~StaticArray() noexcept = default;

    constexpr StaticArray& operator=(const StaticArray& a) = delete;
    constexpr StaticArray& operator=(StaticArray&& a) = delete;

    constexpr const T& operator[](std::size_t i) const noexcept;
private:
    T _data[Size];
    std::size_t _capacity;
    std::size_t _count;

    template<typename Arg>
    constexpr void set_data(std::size_t i, Arg&& arg) noexcept;
    template<typename ... Args, std::size_t ... Indices>
    constexpr void unpack(std::index_sequence<Indices ...>, Args&& ... args) noexcept;
    template<typename ... Args>
    constexpr void create_indexes(Args&& ... args) noexcept;
};


template<typename T, std::size_t Size>
constexpr StaticArray<T, Size>::StaticArray() noexcept :
    _data{T{}},
    _capacity{Size},
    _count{0}
{
}

template<typename T, std::size_t Size>
template<typename ... Args,
         std::enable_if_t<
             std::conjunction_v<
                 std::is_same<T, Args>...
                 >
             > *
         >
constexpr StaticArray<T, Size>::StaticArray(Args&& ... list) noexcept :
    _data{T{}},
    _capacity{Size},
    _count{Size}
{
    static_assert(Size == sizeof ... (list), "Size of array not equal number of elements in the list");
    static_assert(std::conjunction_v<std::is_same<T, Args>... >, "Parameter must be the same type as StaticArray<T>.");
    create_indexes(std::forward<Args>(list) ...);
}

template<typename T, std::size_t Size>
template<typename Arg>
constexpr void StaticArray<T, Size>::set_data(std::size_t i, Arg&& arg) noexcept
{
    _data[i] = arg;
}

template<typename T, std::size_t Size>
template<typename ... Args, std::size_t ... Indices>
constexpr void StaticArray<T, Size>::unpack(std::index_sequence<Indices ...>, Args&& ... args) noexcept
{
    (set_data(Indices, args), ...);
}

template<typename T, std::size_t Size>
template<typename ... Args>
constexpr void StaticArray<T, Size>::create_indexes(Args&& ... args) noexcept
{
    unpack(std::make_index_sequence<Size>{}, std::forward<Args>(args)...);
}

template<typename T, std::size_t Size>
constexpr const T& StaticArray<T, Size>::operator[](std::size_t i) const noexcept
{
    return _data[i];
}


int main()
{
    constexpr StaticArray<unsigned, 10> array = {9u, 8u, 7u, 6u, 5u, 4u, 3u, 2u, 1u, 0u};

    static_assert(array[0] == 9);
    static_assert(array[1] == 8);
    static_assert(array[2] == 7);
    static_assert(array[3] == 6);
    static_assert(array[4] == 5);
    static_assert(array[5] == 4);
    static_assert(array[6] == 3);
    static_assert(array[7] == 2);
    static_assert(array[8] == 1);
    static_assert(array[9] == 0);

    constexpr std::array<unsigned, 10> checker = {9u, 8u, 7u, 6u, 5u, 4u, 3u, 2u, 1u, 0u};

    CONSTEXPR_LOOP<10>([&](auto i) constexpr {
        static_assert(array[i] == checker[i]);
    });

    return 0;
}

And when I compile this using g++-8.3 , I get this error:

.../main.cpp: In instantiation of ‘main()::<lambda(auto:1)> [with auto:1 = std::integral_constant<long unsigned int, 0>]’:
.../main.cpp:9:10:   required from ‘constexpr void __loop(F&&, std::index_sequence<Is ...>) [with F = main()::<lambda(auto:1)>; long unsigned int ...Is = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; std::index_sequence<Is ...> = std::integer_sequence<long unsigned int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>]’
.../main.cpp:16:11:   required from ‘constexpr void CONSTEXPR_LOOP(F&&) [with long unsigned int N = 10; F = main()::<lambda(auto:1)>]’
.../main.cpp:149:6:   required from here
.../main.cpp:148:32: error: non-constant condition for static assertion
         static_assert(array[i] == checker[i]);
                       ~~~~~~~~~^~~~~~~~~~~
.../main.cpp:148:32: error: ‘__closure’ is not a constant expression

After I spent some time to understand what the problem was, I decided to compile this code using g++-7.4 . It compiles successfully without any errors. Clang-6 and g++-9 give me the same result, but as soon as I use g++-8, I get the errors described above. Any idea why this is happening?

Thanks!

[Note] Online example: https://godbolt.org/z/Ig4CCW

[UPDATE] I compiled this code in g++-8 when I added a static specifier to the constexpr variable. It works because:

enter link description here

A lambda expression can use a variable without capturing it if the variable

  • is a non-local variable or has static or thread local storage duration >(in which case the variable cannot be captured)

But if you look at the code below, you will notice that the lambda, which is called from another function, for some reason does not capture the constexpr variable by reference and value in g++-8 . Other compilers do not report any errors.

template<typename F>
constexpr void call(F&& f)
{
    f();
}

int main()
{
    constexpr std::array<unsigned, 1> checker = {1u};
    call([&]() constexpr { static_assert(checker[0] == checker[0]); });
    static constexpr std::array<unsigned, 1> checker2 = {1u};
    call([]() constexpr { static_assert(checker2[0] == checker2[0]); });
    constexpr std::array<unsigned, 1> checker3 = {1u};
    call([=]() constexpr { static_assert(checker3[0] == checker3[0]); });

    return 0;
}

Try it

AFAIK parameter, even in a constexpr function aren't constexpr

constexpr void f(std::size_t n) {
  static_assert(n == 42, "");  // not allowed.
}

Source : https://mpark.github.io/programming/2017/05/26/constexpr-function-parameters/

UPDATE : from comments

I got fooled by the auto . Indeed, since the call is here :

func(std::integral_constant<std::size_t, Is>{}), ...);

auto is an std::integral_constant and it should work

Well, I think this is probably a bug in g++8 . Lambda does not capture the constexpr variable, and the code below very clearly demonstrates this:

template<typename F>
constexpr void call(F&& f)
{
    f();
}

int main()
{
    constexpr std::array<unsigned, 1> checker = {1u};
    call([&]() constexpr { static_assert(checker[0] == checker[0]); }); // compile error
    static constexpr std::array<unsigned, 1> checker2 = {1u};
    call([]() constexpr { static_assert(checker2[0] == checker2[0]); }); // OK!
    constexpr std::array<unsigned, 1> checker3 = {1u};
    call([=]() constexpr { static_assert(checker3[0] == checker3[0]); }); // compile error

    return 0;
}

I did not find any mention of this problem, so I really think that this is a bug in g++8 .

Also, I found three solutions to avoid this error. If you get the same error, you must do one of the three:

  1. Mark your variable as static . Lambda can use a static variable without capturing.
  2. Wrap your constexpr variable in structure using templates:
template<std::size_t Size, typename T, T ... ARGS>
struct ArrayWrapper
{
   static constexpr std::array<T, Size> value = {ARGS ...};
};

constexpr ArrayWrapper<10, unsigned,
    9u, 8u, 7u, 6u, 5u, 4u, 3u, 2u, 1u, 0u> wrapper;
  1. Use another compiler. g++-7 , g++-9 and clang compile this without any errors.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM