Search code examples
c++c++20constexprtemplate-meta-programming

Using constexpr vectors in template parameters (C++20)


Recently, I've been playing around with C++20's new constexpr std::vectors (I'm using GCC v12), and have run into a slight problem (this is actually an extension of my last question, but I thought it would just be better to make a new one). I've been trying to use constexpr std::vectors as members to a class, but this seems like it doesn't work as you cannot annotate them with constexpr and therefore constexpr functions think they can't be evaluated at compile time, so now I am trying to use template parameters instead, like this:

#include <array>

template<int N, std::array<int, N> arr = {1}>
class Test
{
public:
    constexpr int doSomething()
    {
        constexpr const int value = arr[0];
        return value * 100;
    }
};

int main()
{
    Test<10> myTestClass;
    // return the value to prevent it from being optimized away 
    return myTestClass.doSomething();
}

This results in the expected assembly output (simply returning 100 from main):

main:
        mov     eax, 100
        ret

However, something like this doesn't work for std::vectors, even though they can be constexpr now!

#include <vector>

template<std::vector<int> vec = {1}>
class Test
{
public:
    constexpr int doSomething()
    {
        constexpr const int value = vec[0];
        return value * 100;
    }
};

int main()
{
    Test<> myTestClass;
    return myTestClass.doSomething();
}

This throws this error:

<source>:3:35: error: 'std::vector<int>' is not a valid type for a template non-type parameter because it is not structural
    3 | template<std::vector<int> vec = {1}>
      |                                   ^
In file included from /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/vector:64,
                 from <source>:1:
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/stl_vector.h:423:11: note: base class 'std::_Vector_base<int, std::allocator<int> >' is not public
  423 |     class vector : protected _Vector_base<_Tp, _Alloc>
      |           ^~~~~~
<source>: In function 'int main()':
<source>:17:24: error: request for member 'doSomething' in 'myTestClass', which is of non-class type 'int'
   17 |     return myTestClass.doSomething();

How can I do this with vectors, or is it even possible? And, is it possible to make constexpr members, or not?


Solution

  • You still (in C++20 and I don't think there is any change for C++23) can't use a std::vector as a non-type template argument or have any std::vector variable marked constexpr or have any constant expression resulting in a std::vector as value at all.

    The only use case that is allowed now in C++20 that wasn't allowed before is to have a (non-constexpr) std::vector variable or object which is constructed while a constant expression is evaluated and destroyed before the constant evaluation ends.

    This means you can now for example take the function

    constexpr int f() {
        std::vector<int> vec;
        vec.push_back(3);
        vec.push_back(1);
        vec.push_back(2);
        std::sort(vec.begin(), vec.end());
        return vec.front();
    }
    

    add a constexpr on it and use it in constant expression e.g.

    static_assert(f() == 1);
    

    But that's all. It is still very useful, because beforehand you could only use algorithms that don't need any dynamic memory allocation to calculate something at compile-time. That meant that you often couldn't just use the usual runtime algorithm/implementation directly in a compile-time context.

    The same is true for any type that keeps references to dynamically allocated memory. You need to destroy them during the constant expression evaluation, i.e. they must be temporaries or local variables in a function or return values which are not stored in a runtime context.

    In the specific case of non-type template arguments the situation is even stricter. Not all types that you could make a constexpr variable of, can be used as non-type template arguments. There are much stricter restrictions. They must be so-called structural types.

    These are for example fundamental types such as arithmetic types, pointers, etc. A class type is a structural type if it is a literal type and also has only non-static data members which are public and non-mutable as well as all of them, recursively, structural types as well.

    I think it is clear that std::vector doesn't satisfy these requirements. std::array is explicitly specified to be a structural type, which is why you are allowed to use it as non-type template argument.