Constexpr doesn't get evaluated when computed using const variable outside function

Viewed 163

I present two code snippets, one of them compiles and the other doesn't.

The one that doesn't compile:

class Solution {
    public:
    const int MAX_NUM = 100;
    const int MAX_SIZE = 200;
    bool canPartition(vector<int>& nums) {
        bitset<(MAX_NUM*MAX_SIZE)/2 + 1> bits(1);
        
        int sum = 0;
        for(int num: nums)
        {
            sum += num;
            bits |= bits << num;
        }
        return !(sum % 2) and bits[sum/2];
    }
};

Gives the errors:

error: non-type template argument is not a constant expression

implicit use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function

One that does:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        const int MAX_NUM = 100;
        const int MAX_SIZE = 200;
        bitset<(MAX_NUM*MAX_SIZE)/2 + 1> bits(1);
        
        int sum = 0;
        for(int num: nums)
        {
            sum += num;
            bits |= bits << num;
        }
        return !(sum % 2) and bits[sum/2];
    }
};

I read constexpr documentation and found two things which might be issue here:

constexpr must immediately initialized.

It must have constant destruction, i.e. it is not of class type

Could you point out what's the issue here and help me understand the problem?

2 Answers

The error messages explain what's going on quite well. The template argument to the declaration of bits needs to be a constant expression. However, if you use non-static members like MAX_NUM inside a non-constexpr member function, you end up evaluating the this pointer, which is not allowed. ref:

A core constant expression is any expression whose evaluation would not evaluate any one of the following:

  1. the this pointer, except in a constexpr function that is being evaluated as part of the expression

When the variables like MAX_NUM are declared inside the member function, they're not members of the class, and so they can be used as constant expressions, which is why the first version compiles.

The problem is that since Templates are evaluated at compile time, their arguments cannot be anything that the compiler can't "predict". In your first code the member variables MAX_NUM and MAX_SIZE are const values, meaning they cannot be changed after an instance of the class Solution is made and they are initialized. But for each instance of Solution they can still be initialized with different, unpredictable values in a constructor. The default value that you set them equal to (respectively 100 and 200) are only used if they aren't initialized during creation of an instance. Take a look at the code below:

#include <iostream>

using std::cout;
using std::cin;
using std::endl;

class A
{
private:
    const int m_foo = 1;
public:
    A() {}
    A(int num) : m_foo(num) {}
    int foo() { return m_foo; }
};

int main()
{
    A a1;         //a1.m_foo initialized with default value
    cout << "Enter desired m_foo for a2: ";
    int foo;
    cin >> foo;
    A a2(foo);    //a2.m_foo initialized with user input
    cout << "m_foo for a1: " << a1.foo() << endl 
        << "m_foo for a2: " << a2.foo();
    return 0;
}

As you can see the const member variable called m_foo can be any value entered by the user.

However, when defining a const variable inside the scope of a function, that value cannot be changed and is therefore qualified as a template argument, i.e. it is similar to a constexpr.

Related