John Ilacqua
John Ilacqua

Reputation: 977

compile-time validation of constexpr std::initializer_list

I'm trying to implement compile-time validation of some hardcoded values. I have the following simplified attempt:

using Type = std::initializer_list<int>;

constexpr bool all_positive(const Type& list)
{
    bool all_positive = true;
    for (const auto& elem : list)
    {
        all_positive &= (elem > 0);
    }
    return all_positive;
}

int main()
{
    static constexpr Type num_list{ 1000, 10000, 100 };

    static_assert(all_positive(num_list), "all values should be positive");

    return 0;
}

gcc compiles this and works exactly as I expected, but clang fails compilation with the error:

static_assert_test.cc:79:16: error: static_assert expression is not an integral constant expression
    static_assert(all_positive(num_list), "all values should be positive");
                  ^~~~~~~~~~~~~~~~~~~~~~
static_assert_test.cc:54:20: note: read of temporary is not allowed in a constant expression outside the expression that created the temporary
            all_positive &= (elem > 0);
                             ^
static_assert_test.cc:79:16: note: in call to 'all_positive(num_list)'
    static_assert(all_positive(num_list), "all values should be positive");
                  ^
static_assert_test.cc:77:32: note: temporary created here
    static constexpr Type num_list{ 1000, 10000, 100 };

What's the expected behaviour here? Should this compile or not? And if not, is there an alternative way to validate hard-coded values?

Upvotes: 12

Views: 1687

Answers (2)

xskxzr
xskxzr

Reputation: 13040

As Yola's answer says, a temporary array is created, and the expression elem > 0 tries to apply a lvalue-to-rvalue conversion to elem. Now we refer to the standard [expr.const]/2:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • ...

  • an lvalue-to-rvalue conversion unless it is applied to

    • a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or

    • a non-volatile glvalue that refers to a subobject of a string literal, or

    • a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or

    • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;

  • ...

Note the first bullet does not apply here because elem does not refer to a complete object (it is a subobject of an array). The third bullet does not apply too because the temporary array is not defined with constexpr though it is a const object. As a result, all_positive(num_list) fails to become a constant expression.

The key is that accessing an element of a const, but not constexpr, array is not permitted in a constant expression, though the values of these elements may be able to be determined at compile time. The following code snippet shows this issue:

const int ci[1] = {0};
const int &ri = ci[0];
constexpr int i = ri; // error

Upvotes: 3

Yola
Yola

Reputation: 19031

The problem is that you are trying to use temporary array to initialize your constexpr.

An object of type std::initializer_list is constructed from an initializer list as if the implementation generated and materialized (7.4) a prvalue of type “array of N const E”, where N is the number of elements in the initializer list.

But this temporary array is not a constant per se. It could work like this:

static constexpr array<int,4> arr = { 1000, 10000, 100 };
static constexpr Type num_list(&arr[0], &arr[3]);
static_assert(all_positive(num_list), "all values should be positive");

Upvotes: 2

Related Questions