std::array:size() as a const in gcc 11

151 views Asked by At

I have this example code

#include <iostream>
#include <array>
#include <bitset>

template <class T, std::size_t N>
class Booth
{
  public:
    int function(const std::array<T, N> & data) {
        std::bitset<data.size()> bit_set{};

        return 0;
    };

  private:
};

int main()
{
    std::cout << "std::array test\n";

    std::array<int, 3> data2{2};
    Booth<int, 3>booth{};
    std::cout << booth.function(data2) << std::endl;

    return 0;
}

It compiles fine in [OnlineGDB][1]. And in the older versions of gcc. However, it started failing in gcc 11:

g++ -Wall -Wconversion -lstdc++ -pthread -std=c++17 -o array_size src/array_size.cpp
src/array_size.cpp: In function ‘int function(const std::array<int, 42>&)’:
src/array_size.cpp:22:28: error: ‘data’ is not a constant expression
   22 |     std::bitset<data.size()> bit_set{};
      |                            ^
src/array_size.cpp:22:26: note: in template argument for type ‘long unsigned int’
   22 |     std::bitset<data.size()> bit_set{};
      |                 ~~~~~~~~~^~

The code is the same. The language version is the same. I know how to fix this error. I just would like to know what was changed in the compiler? What is the reason for this change and thus the failure?

2

There are 2 answers

4
user17732522 On BEST ANSWER

The code has not been valid until recently. If the compiler allowed it before without diagnostic, then the compiler wasn't conforming to the standard at the time.

The issue is simply that data in function is a reference. If it was taken by-value instead, then it would be fine.

One of the rules that disqualify an expression like data.size() from being a constant expression, as required for template arguments, is that any reference named in the expression (and whose lifetime didn't start during the evaluation of the constant expression) is itself initialized with a constant expression, even if no lvalue-to-rvalue conversion is applied to the referenced object and the identity of the referenced object is irrelevant. A function parameter isn't initialized with a constant expression and so data cannot be used in a constant expression inside the function in any capacity.

There isn't really any good reason for this restriction to be that strict. You only want to call .size() which just returns a constant determined from the type. It doesn't need any information about the actual object on which it is called.

So the restriction has been softened in the latest draft for C++23 to allow for such use and the fix will also be considered a defect report against previous C++ revisions. So in the future the compilers should again allow your code regardless of the chosen C++ version.

1
nkrt On

In C++17 and later, the std::array class template provides a size() member function that returns the size of the array. This function is constexpr, which means it can be used at compile time and the value of the size can be used as a template argument.

The issue in the code is that the std::bitset constructor expects a unsigned long long as its argument, not a size_t. Instead of using data.size(), you can explicitly convert the size_t to a unsigned long long using static_cast<unsigned long long>() as follows:

std::bitset<static_cast<unsigned long long>(data.size())> bit_set{};

This should allow the code to compile with GCC 11 and later versions.