12

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?

John Ilacqua
  • 906
  • 5
  • 19
  • Personally, I'd probably not use `initializer_list`, but instead use `std::array`. It does compile if you switch it out for `std::array`: https://godbolt.org/g/jbJJdx – Justin Feb 08 '18 at 02:54
  • [`std::initializer_list` on cppreference](http://en.cppreference.com/w/cpp/utility/initializer_list). I think the confusion is over the "since C++14" about that the underlying array is a temporary array. IIUC, this *should* compile, because the lifetime of the temporary should be extended to the lifetime of `num_list`. – Justin Feb 08 '18 at 03:05
  • If this is a compiler bug related to lifetime extension and `std::initializer_list`, it should be filed for MSVC too: https://godbolt.org/g/ajaaj8 – Justin Feb 08 '18 at 03:07
  • The [relevant part of the standard](http://eel.is/c++draft/dcl.init#list-6). I am not sure enough about how `num_list` being `static` matters or whether I'm reading the standard correctly to post an answer. If I *am* interpreting the standard correctly, this code should be accepted – Justin Feb 08 '18 at 03:17
  • I wonder how can you create static variable from temporary value (array here). – Yola Feb 08 '18 at 06:08
  • I'm thinking about whether this should get the [language-lawyer] tag.... You aren't exactly worried about that, but you inadvertently stumbled upon something that is very language-lawyer-like – Justin Feb 08 '18 at 07:12
  • @Justin I tested with `std::array`, but it doesn't compile under c++14 for gcc or clang; it'll only compile with c++1z. – John Ilacqua Feb 09 '18 at 00:26

2 Answers2

3

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
xskxzr
  • 12,442
  • 12
  • 37
  • 77
  • 1
    Interesting. I can get the same behavior [using something similar to `std::initializer_list`](https://godbolt.org/g/uqFcoF), just as you show here. Weird that gcc doesn't catch it when it comes to `std::initializer_list`; maybe it ends up declaring the temporary array as `constexpr` (which would fix the compile error in the example I linked here)... – Justin Feb 08 '18 at 07:51
  • Thanks for the thorough answer with reference to the standard. Do you have an answer to the last part of my question? Is there an alternative that will compile under c++14? (As mentioned in another comment, replacing `std::initializer_list` with `std::array` work for c++17 but not 14.) – John Ilacqua Feb 09 '18 at 00:37
2

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");
Yola
  • 18,496
  • 11
  • 65
  • 106
  • It seems to me like you are implying that I can't use `std::initializer_list` as `constexpr`. If that were the case, it shouldn't have all its functions marked `constexpr`. What do you mean by "prvalue is not a compile-time concept"? You reference the standard, can you reference it for the claim that that makes the OP's code invalid? – Justin Feb 08 '18 at 06:58
  • @Justin you can `std::initializer_list` as `constexpr`. I provided the example. I removed part about prvalue and will think about it. The memory for the array is not allocated in compile time. – Yola Feb 08 '18 at 07:07
  • So lifetime extension of the array temporary doesn't occur because this is `constexpr`? – Justin Feb 08 '18 at 07:10
  • @Justin I know about lifetime extension because of bounding to const reference. But this happens while application is running. – Yola Feb 08 '18 at 07:12
  • 1
    @Justin and again that is runtime concept - lifetime extension. See nice example [here](https://stackoverflow.com/questions/2784262/does-a-const-reference-prolong-the-life-of-a-temporary) – Yola Feb 08 '18 at 07:18
  • @Justin i updated, i think it is much clearer now. I was mistaken from the start. Thank you for the disscussion – Yola Feb 08 '18 at 07:25