Why is an initializer_list of enum values not considered a constant expression?












13















In the following code (tested locally and on Wandbox):



#include <iostream>

enum Types
{
A, B, C, D
};

void print(std::initializer_list<Types> types)
{
for (auto type : types)
{
std::cout << type << std::endl;
}
}

int main()
{
constexpr auto const group1 = { A, D };
print(group1);
return 0;
}


MSVC 15.8.5 fails to compile with:



error C2131: expression did not evaluate to a constant
note: failure was caused by a read of a variable outside its lifetime
note: see usage of '$S1'


(all referring to the line containing constexpr)



Clang 8 (HEAD) reports:



error: constexpr variable 'group1' must be initialized by a constant expression
constexpr auto const group1 = { A, D };
^ ~~~~~~~~
note: pointer to subobject of temporary is not a constant expression
note: temporary created here
constexpr auto const group1 = { A, D };
^


gcc 9 (HEAD) reports:



In function 'int main()':
error: 'const std::initializer_list<const Types>{((const Types*)(&<anonymous>)), 2}' is not a constant expression
18 | constexpr auto const group1 = { A, D };
| ^
error: could not convert 'group1' from 'initializer_list<const Types>' to 'initializer_list<Types>'
19 | print(group1);
| ^~~~~~
| |
| initializer_list<const Types>


Why?



Firstly, they all apparently consider enum-ids to be non-constant, despite them obviously actually being well-known compile-time constant values.



Secondly, MSVC complains about read outside lifetime, but the lifetime of group1 and its values should extend throughout its usage in print.



Thirdly, gcc has a weird const-vs-non-const complaint that I can't make any sense of, since initialiser lists are always const.



Finally, all but gcc will happily compile and run this code without any problems if constexpr is removed. Granted it is not necessary in this case, but I can't see any good reason for it not to work.



Meanwhile gcc will only compile and run the code if the parameter type is changed to std::initializer_list<const Types> -- and making this change causes it to fail to compile in both MSVC and clang.



(Interestingly: gcc 8, with the parameter type change, does successfully compile and run the code including constexpr, where gcc 9 errors out.)





FWIW, changing the declaration to this:



    constexpr auto const group1 = std::array<Types, 2>{ A, D };


Does compile and run on all three compilers. So it is probably the initializer_list itself that is misbehaving rather than the enum values. But the syntax is more annoying. (It's slightly less annoying with a suitable make_array implementation, but I still don't see why the original isn't valid.)





    constexpr auto const group1 = std::array{ A, D };


Also works, thanks to C++17 template induction. Though now print can't take an initializer_list; it has to be templated on a generic container/iterator concept, which is inconvenient.










share|improve this question

























  • I like the question, I wonder if it is a bug (can be logged on the trackers of the compilers?). Just for curiousity, how does it behave with Enum-class?

    – JVApen
    Jan 17 at 7:07











  • Enum-class behaves exactly the same, except that inside print you need an explicit cast to int, since the implicit conversion is disabled.

    – Miral
    Jan 17 at 22:55
















13















In the following code (tested locally and on Wandbox):



#include <iostream>

enum Types
{
A, B, C, D
};

void print(std::initializer_list<Types> types)
{
for (auto type : types)
{
std::cout << type << std::endl;
}
}

int main()
{
constexpr auto const group1 = { A, D };
print(group1);
return 0;
}


MSVC 15.8.5 fails to compile with:



error C2131: expression did not evaluate to a constant
note: failure was caused by a read of a variable outside its lifetime
note: see usage of '$S1'


(all referring to the line containing constexpr)



Clang 8 (HEAD) reports:



error: constexpr variable 'group1' must be initialized by a constant expression
constexpr auto const group1 = { A, D };
^ ~~~~~~~~
note: pointer to subobject of temporary is not a constant expression
note: temporary created here
constexpr auto const group1 = { A, D };
^


gcc 9 (HEAD) reports:



In function 'int main()':
error: 'const std::initializer_list<const Types>{((const Types*)(&<anonymous>)), 2}' is not a constant expression
18 | constexpr auto const group1 = { A, D };
| ^
error: could not convert 'group1' from 'initializer_list<const Types>' to 'initializer_list<Types>'
19 | print(group1);
| ^~~~~~
| |
| initializer_list<const Types>


Why?



Firstly, they all apparently consider enum-ids to be non-constant, despite them obviously actually being well-known compile-time constant values.



Secondly, MSVC complains about read outside lifetime, but the lifetime of group1 and its values should extend throughout its usage in print.



Thirdly, gcc has a weird const-vs-non-const complaint that I can't make any sense of, since initialiser lists are always const.



Finally, all but gcc will happily compile and run this code without any problems if constexpr is removed. Granted it is not necessary in this case, but I can't see any good reason for it not to work.



Meanwhile gcc will only compile and run the code if the parameter type is changed to std::initializer_list<const Types> -- and making this change causes it to fail to compile in both MSVC and clang.



(Interestingly: gcc 8, with the parameter type change, does successfully compile and run the code including constexpr, where gcc 9 errors out.)





FWIW, changing the declaration to this:



    constexpr auto const group1 = std::array<Types, 2>{ A, D };


Does compile and run on all three compilers. So it is probably the initializer_list itself that is misbehaving rather than the enum values. But the syntax is more annoying. (It's slightly less annoying with a suitable make_array implementation, but I still don't see why the original isn't valid.)





    constexpr auto const group1 = std::array{ A, D };


Also works, thanks to C++17 template induction. Though now print can't take an initializer_list; it has to be templated on a generic container/iterator concept, which is inconvenient.










share|improve this question

























  • I like the question, I wonder if it is a bug (can be logged on the trackers of the compilers?). Just for curiousity, how does it behave with Enum-class?

    – JVApen
    Jan 17 at 7:07











  • Enum-class behaves exactly the same, except that inside print you need an explicit cast to int, since the implicit conversion is disabled.

    – Miral
    Jan 17 at 22:55














13












13








13


3






In the following code (tested locally and on Wandbox):



#include <iostream>

enum Types
{
A, B, C, D
};

void print(std::initializer_list<Types> types)
{
for (auto type : types)
{
std::cout << type << std::endl;
}
}

int main()
{
constexpr auto const group1 = { A, D };
print(group1);
return 0;
}


MSVC 15.8.5 fails to compile with:



error C2131: expression did not evaluate to a constant
note: failure was caused by a read of a variable outside its lifetime
note: see usage of '$S1'


(all referring to the line containing constexpr)



Clang 8 (HEAD) reports:



error: constexpr variable 'group1' must be initialized by a constant expression
constexpr auto const group1 = { A, D };
^ ~~~~~~~~
note: pointer to subobject of temporary is not a constant expression
note: temporary created here
constexpr auto const group1 = { A, D };
^


gcc 9 (HEAD) reports:



In function 'int main()':
error: 'const std::initializer_list<const Types>{((const Types*)(&<anonymous>)), 2}' is not a constant expression
18 | constexpr auto const group1 = { A, D };
| ^
error: could not convert 'group1' from 'initializer_list<const Types>' to 'initializer_list<Types>'
19 | print(group1);
| ^~~~~~
| |
| initializer_list<const Types>


Why?



Firstly, they all apparently consider enum-ids to be non-constant, despite them obviously actually being well-known compile-time constant values.



Secondly, MSVC complains about read outside lifetime, but the lifetime of group1 and its values should extend throughout its usage in print.



Thirdly, gcc has a weird const-vs-non-const complaint that I can't make any sense of, since initialiser lists are always const.



Finally, all but gcc will happily compile and run this code without any problems if constexpr is removed. Granted it is not necessary in this case, but I can't see any good reason for it not to work.



Meanwhile gcc will only compile and run the code if the parameter type is changed to std::initializer_list<const Types> -- and making this change causes it to fail to compile in both MSVC and clang.



(Interestingly: gcc 8, with the parameter type change, does successfully compile and run the code including constexpr, where gcc 9 errors out.)





FWIW, changing the declaration to this:



    constexpr auto const group1 = std::array<Types, 2>{ A, D };


Does compile and run on all three compilers. So it is probably the initializer_list itself that is misbehaving rather than the enum values. But the syntax is more annoying. (It's slightly less annoying with a suitable make_array implementation, but I still don't see why the original isn't valid.)





    constexpr auto const group1 = std::array{ A, D };


Also works, thanks to C++17 template induction. Though now print can't take an initializer_list; it has to be templated on a generic container/iterator concept, which is inconvenient.










share|improve this question
















In the following code (tested locally and on Wandbox):



#include <iostream>

enum Types
{
A, B, C, D
};

void print(std::initializer_list<Types> types)
{
for (auto type : types)
{
std::cout << type << std::endl;
}
}

int main()
{
constexpr auto const group1 = { A, D };
print(group1);
return 0;
}


MSVC 15.8.5 fails to compile with:



error C2131: expression did not evaluate to a constant
note: failure was caused by a read of a variable outside its lifetime
note: see usage of '$S1'


(all referring to the line containing constexpr)



Clang 8 (HEAD) reports:



error: constexpr variable 'group1' must be initialized by a constant expression
constexpr auto const group1 = { A, D };
^ ~~~~~~~~
note: pointer to subobject of temporary is not a constant expression
note: temporary created here
constexpr auto const group1 = { A, D };
^


gcc 9 (HEAD) reports:



In function 'int main()':
error: 'const std::initializer_list<const Types>{((const Types*)(&<anonymous>)), 2}' is not a constant expression
18 | constexpr auto const group1 = { A, D };
| ^
error: could not convert 'group1' from 'initializer_list<const Types>' to 'initializer_list<Types>'
19 | print(group1);
| ^~~~~~
| |
| initializer_list<const Types>


Why?



Firstly, they all apparently consider enum-ids to be non-constant, despite them obviously actually being well-known compile-time constant values.



Secondly, MSVC complains about read outside lifetime, but the lifetime of group1 and its values should extend throughout its usage in print.



Thirdly, gcc has a weird const-vs-non-const complaint that I can't make any sense of, since initialiser lists are always const.



Finally, all but gcc will happily compile and run this code without any problems if constexpr is removed. Granted it is not necessary in this case, but I can't see any good reason for it not to work.



Meanwhile gcc will only compile and run the code if the parameter type is changed to std::initializer_list<const Types> -- and making this change causes it to fail to compile in both MSVC and clang.



(Interestingly: gcc 8, with the parameter type change, does successfully compile and run the code including constexpr, where gcc 9 errors out.)





FWIW, changing the declaration to this:



    constexpr auto const group1 = std::array<Types, 2>{ A, D };


Does compile and run on all three compilers. So it is probably the initializer_list itself that is misbehaving rather than the enum values. But the syntax is more annoying. (It's slightly less annoying with a suitable make_array implementation, but I still don't see why the original isn't valid.)





    constexpr auto const group1 = std::array{ A, D };


Also works, thanks to C++17 template induction. Though now print can't take an initializer_list; it has to be templated on a generic container/iterator concept, which is inconvenient.







c++ language-lawyer c++17 constexpr






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Jan 17 at 22:54







Miral

















asked Jan 17 at 6:32









MiralMiral

8,18323271




8,18323271













  • I like the question, I wonder if it is a bug (can be logged on the trackers of the compilers?). Just for curiousity, how does it behave with Enum-class?

    – JVApen
    Jan 17 at 7:07











  • Enum-class behaves exactly the same, except that inside print you need an explicit cast to int, since the implicit conversion is disabled.

    – Miral
    Jan 17 at 22:55



















  • I like the question, I wonder if it is a bug (can be logged on the trackers of the compilers?). Just for curiousity, how does it behave with Enum-class?

    – JVApen
    Jan 17 at 7:07











  • Enum-class behaves exactly the same, except that inside print you need an explicit cast to int, since the implicit conversion is disabled.

    – Miral
    Jan 17 at 22:55

















I like the question, I wonder if it is a bug (can be logged on the trackers of the compilers?). Just for curiousity, how does it behave with Enum-class?

– JVApen
Jan 17 at 7:07





I like the question, I wonder if it is a bug (can be logged on the trackers of the compilers?). Just for curiousity, how does it behave with Enum-class?

– JVApen
Jan 17 at 7:07













Enum-class behaves exactly the same, except that inside print you need an explicit cast to int, since the implicit conversion is disabled.

– Miral
Jan 17 at 22:55





Enum-class behaves exactly the same, except that inside print you need an explicit cast to int, since the implicit conversion is disabled.

– Miral
Jan 17 at 22:55












2 Answers
2






active

oldest

votes


















9














When you initialize a std::initializer_list it happens like this:




[dcl.init.list] (emphasis mine)



5 An object of type std​::​initializer_­list is constructed
from an initializer list as if the implementation generated and
materialized a prvalue of type “array of N const E”
, where N is the
number of elements in the initializer list. Each element of that array
is copy-initialized with the corresponding element of the initializer
list, and the std​::​initializer_­list object is constructed to
refer to that array. [ Note: A constructor or conversion function
selected for the copy shall be accessible in the context of the
initializer list.  — end note ] If a narrowing conversion is required
to initialize any of the elements, the program is ill-formed.
[ Example:



struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };


The initialization will be implemented in a way roughly equivalent to
this:



const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));


assuming that the implementation can construct an initializer_­list
object with a pair of pointers.  — end example ]




How that temporary array gets used to initialize the std::initializer_list is what determines if the initializer_list is initialized with a constant expression. Ultimately, according to example (despite being non-normative), that initialization is going to take the address of the array, or its first element, which will produce a value of a pointer type. And that is not a valid constant expression.




[expr.const] (emphasis mine)



5 A constant expression is either a glvalue core constant
expression that refers to an entity that is a permitted result of a
constant expression (as defined below), or a prvalue core constant
expression whose value satisfies the following constraints:




  • if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a
    constant expression,

  • if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such
    an object ([expr.add]), the address of a function, or a null pointer
    value, and

  • if the value is an object of class or array type, each subobject satisfies these constraints for the value.


An entity is a permitted result of a constant expression if it is an
object with static storage duration that is either not a temporary
object or is a temporary object whose value satisfies the above
constraints, or it is a function.




If the array was however a static object, then that initializer would constitute a valid constant expression that can be used to initialize a constexpr object. Since std::initializer_list has an effect of lifetime extension on that temporary by [dcl.init.list]/6, when you declare group1 as a static object, clang and gcc seem allocate the array as a static object too, which makes the initialization's well-formedness subject only to whether or not std::initializer_list is a literal type and the constructor being used is constexpr.



Ultimately, it's all a bit murky.






share|improve this answer

































    5














    It appears std::initializer_list does not yet (in C++17) fulfill the requirements of literal type (which is a requirement the type of a constexpr variable has to satisfy).



    A discussion on whether it does so in C++14 is found in this post: Why isn't std::initializer_list defined as a literal type?
    which itself was a followup to the post discussing Is it legal to declare a constexpr initializer_list object?



    I compared the citations provided in the C++14 related post (of the C++14 standard) to the final working draft (of the C++17 standard) and they are the same.
    So there is no explicit requirement that std::initializer_list should be a literal type.



    Citations from the final working draft of C++17 (n4659):



    [basic.types]/10.5




    (10.5) a possibly cv-qualified class type (Clause 12) that has all of the
    following properties:

    (10.5.1) — it has a trivial destructor,

    (10.5.2) — it is either a closure type (8.1.5.1), an aggregate type (11.6.1),
    or has at least one constexpr constructor or constructor template
    (possibly inherited (10.3.3) from a base class) that is not a copy or
    move constructor,

    (10.5.3) — if it is a union, at least one of its non-static data members is of non-volatile literal type, and

    (10.5.4) — if it is not a union, all of its non-static data members and base
    classes are of non-volatile literal types.




    [initializer_list.syn]/1





    1. An object of type initializer_list provides access to an array of objects of type const E. [ Note:A pair of pointers or a pointer plus a length would be obvious representations for initializer_list. initializer_list is used to implement initializer lists as specified in 11.6.4. Copying an initializer list does not copy the underlying elements. —end note ]




    That is the reason why it is not legal to declare a constexpr initializer_list object.






    share|improve this answer


























    • Then how is it that the std::array, which takes an initializer_list constructor parameter, does satisfy being a constexpr? Why is it that the compiler can't similarly construct a backing array and an initializer_list to it as constexpr?

      – Miral
      Jan 17 at 22:35











    • I suppose technically that's aggregate initialisation, rather than actually using an initializer_list constructor. But it's still weird that this is treated differently.

      – Miral
      Jan 17 at 22:48











    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54230229%2fwhy-is-an-initializer-list-of-enum-values-not-considered-a-constant-expression%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    9














    When you initialize a std::initializer_list it happens like this:




    [dcl.init.list] (emphasis mine)



    5 An object of type std​::​initializer_­list is constructed
    from an initializer list as if the implementation generated and
    materialized a prvalue of type “array of N const E”
    , where N is the
    number of elements in the initializer list. Each element of that array
    is copy-initialized with the corresponding element of the initializer
    list, and the std​::​initializer_­list object is constructed to
    refer to that array. [ Note: A constructor or conversion function
    selected for the copy shall be accessible in the context of the
    initializer list.  — end note ] If a narrowing conversion is required
    to initialize any of the elements, the program is ill-formed.
    [ Example:



    struct X {
    X(std::initializer_list<double> v);
    };
    X x{ 1,2,3 };


    The initialization will be implemented in a way roughly equivalent to
    this:



    const double __a[3] = {double{1}, double{2}, double{3}};
    X x(std::initializer_list<double>(__a, __a+3));


    assuming that the implementation can construct an initializer_­list
    object with a pair of pointers.  — end example ]




    How that temporary array gets used to initialize the std::initializer_list is what determines if the initializer_list is initialized with a constant expression. Ultimately, according to example (despite being non-normative), that initialization is going to take the address of the array, or its first element, which will produce a value of a pointer type. And that is not a valid constant expression.




    [expr.const] (emphasis mine)



    5 A constant expression is either a glvalue core constant
    expression that refers to an entity that is a permitted result of a
    constant expression (as defined below), or a prvalue core constant
    expression whose value satisfies the following constraints:




    • if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a
      constant expression,

    • if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such
      an object ([expr.add]), the address of a function, or a null pointer
      value, and

    • if the value is an object of class or array type, each subobject satisfies these constraints for the value.


    An entity is a permitted result of a constant expression if it is an
    object with static storage duration that is either not a temporary
    object or is a temporary object whose value satisfies the above
    constraints, or it is a function.




    If the array was however a static object, then that initializer would constitute a valid constant expression that can be used to initialize a constexpr object. Since std::initializer_list has an effect of lifetime extension on that temporary by [dcl.init.list]/6, when you declare group1 as a static object, clang and gcc seem allocate the array as a static object too, which makes the initialization's well-formedness subject only to whether or not std::initializer_list is a literal type and the constructor being used is constexpr.



    Ultimately, it's all a bit murky.






    share|improve this answer






























      9














      When you initialize a std::initializer_list it happens like this:




      [dcl.init.list] (emphasis mine)



      5 An object of type std​::​initializer_­list is constructed
      from an initializer list as if the implementation generated and
      materialized a prvalue of type “array of N const E”
      , where N is the
      number of elements in the initializer list. Each element of that array
      is copy-initialized with the corresponding element of the initializer
      list, and the std​::​initializer_­list object is constructed to
      refer to that array. [ Note: A constructor or conversion function
      selected for the copy shall be accessible in the context of the
      initializer list.  — end note ] If a narrowing conversion is required
      to initialize any of the elements, the program is ill-formed.
      [ Example:



      struct X {
      X(std::initializer_list<double> v);
      };
      X x{ 1,2,3 };


      The initialization will be implemented in a way roughly equivalent to
      this:



      const double __a[3] = {double{1}, double{2}, double{3}};
      X x(std::initializer_list<double>(__a, __a+3));


      assuming that the implementation can construct an initializer_­list
      object with a pair of pointers.  — end example ]




      How that temporary array gets used to initialize the std::initializer_list is what determines if the initializer_list is initialized with a constant expression. Ultimately, according to example (despite being non-normative), that initialization is going to take the address of the array, or its first element, which will produce a value of a pointer type. And that is not a valid constant expression.




      [expr.const] (emphasis mine)



      5 A constant expression is either a glvalue core constant
      expression that refers to an entity that is a permitted result of a
      constant expression (as defined below), or a prvalue core constant
      expression whose value satisfies the following constraints:




      • if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a
        constant expression,

      • if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such
        an object ([expr.add]), the address of a function, or a null pointer
        value, and

      • if the value is an object of class or array type, each subobject satisfies these constraints for the value.


      An entity is a permitted result of a constant expression if it is an
      object with static storage duration that is either not a temporary
      object or is a temporary object whose value satisfies the above
      constraints, or it is a function.




      If the array was however a static object, then that initializer would constitute a valid constant expression that can be used to initialize a constexpr object. Since std::initializer_list has an effect of lifetime extension on that temporary by [dcl.init.list]/6, when you declare group1 as a static object, clang and gcc seem allocate the array as a static object too, which makes the initialization's well-formedness subject only to whether or not std::initializer_list is a literal type and the constructor being used is constexpr.



      Ultimately, it's all a bit murky.






      share|improve this answer




























        9












        9








        9







        When you initialize a std::initializer_list it happens like this:




        [dcl.init.list] (emphasis mine)



        5 An object of type std​::​initializer_­list is constructed
        from an initializer list as if the implementation generated and
        materialized a prvalue of type “array of N const E”
        , where N is the
        number of elements in the initializer list. Each element of that array
        is copy-initialized with the corresponding element of the initializer
        list, and the std​::​initializer_­list object is constructed to
        refer to that array. [ Note: A constructor or conversion function
        selected for the copy shall be accessible in the context of the
        initializer list.  — end note ] If a narrowing conversion is required
        to initialize any of the elements, the program is ill-formed.
        [ Example:



        struct X {
        X(std::initializer_list<double> v);
        };
        X x{ 1,2,3 };


        The initialization will be implemented in a way roughly equivalent to
        this:



        const double __a[3] = {double{1}, double{2}, double{3}};
        X x(std::initializer_list<double>(__a, __a+3));


        assuming that the implementation can construct an initializer_­list
        object with a pair of pointers.  — end example ]




        How that temporary array gets used to initialize the std::initializer_list is what determines if the initializer_list is initialized with a constant expression. Ultimately, according to example (despite being non-normative), that initialization is going to take the address of the array, or its first element, which will produce a value of a pointer type. And that is not a valid constant expression.




        [expr.const] (emphasis mine)



        5 A constant expression is either a glvalue core constant
        expression that refers to an entity that is a permitted result of a
        constant expression (as defined below), or a prvalue core constant
        expression whose value satisfies the following constraints:




        • if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a
          constant expression,

        • if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such
          an object ([expr.add]), the address of a function, or a null pointer
          value, and

        • if the value is an object of class or array type, each subobject satisfies these constraints for the value.


        An entity is a permitted result of a constant expression if it is an
        object with static storage duration that is either not a temporary
        object or is a temporary object whose value satisfies the above
        constraints, or it is a function.




        If the array was however a static object, then that initializer would constitute a valid constant expression that can be used to initialize a constexpr object. Since std::initializer_list has an effect of lifetime extension on that temporary by [dcl.init.list]/6, when you declare group1 as a static object, clang and gcc seem allocate the array as a static object too, which makes the initialization's well-formedness subject only to whether or not std::initializer_list is a literal type and the constructor being used is constexpr.



        Ultimately, it's all a bit murky.






        share|improve this answer















        When you initialize a std::initializer_list it happens like this:




        [dcl.init.list] (emphasis mine)



        5 An object of type std​::​initializer_­list is constructed
        from an initializer list as if the implementation generated and
        materialized a prvalue of type “array of N const E”
        , where N is the
        number of elements in the initializer list. Each element of that array
        is copy-initialized with the corresponding element of the initializer
        list, and the std​::​initializer_­list object is constructed to
        refer to that array. [ Note: A constructor or conversion function
        selected for the copy shall be accessible in the context of the
        initializer list.  — end note ] If a narrowing conversion is required
        to initialize any of the elements, the program is ill-formed.
        [ Example:



        struct X {
        X(std::initializer_list<double> v);
        };
        X x{ 1,2,3 };


        The initialization will be implemented in a way roughly equivalent to
        this:



        const double __a[3] = {double{1}, double{2}, double{3}};
        X x(std::initializer_list<double>(__a, __a+3));


        assuming that the implementation can construct an initializer_­list
        object with a pair of pointers.  — end example ]




        How that temporary array gets used to initialize the std::initializer_list is what determines if the initializer_list is initialized with a constant expression. Ultimately, according to example (despite being non-normative), that initialization is going to take the address of the array, or its first element, which will produce a value of a pointer type. And that is not a valid constant expression.




        [expr.const] (emphasis mine)



        5 A constant expression is either a glvalue core constant
        expression that refers to an entity that is a permitted result of a
        constant expression (as defined below), or a prvalue core constant
        expression whose value satisfies the following constraints:




        • if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a
          constant expression,

        • if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such
          an object ([expr.add]), the address of a function, or a null pointer
          value, and

        • if the value is an object of class or array type, each subobject satisfies these constraints for the value.


        An entity is a permitted result of a constant expression if it is an
        object with static storage duration that is either not a temporary
        object or is a temporary object whose value satisfies the above
        constraints, or it is a function.




        If the array was however a static object, then that initializer would constitute a valid constant expression that can be used to initialize a constexpr object. Since std::initializer_list has an effect of lifetime extension on that temporary by [dcl.init.list]/6, when you declare group1 as a static object, clang and gcc seem allocate the array as a static object too, which makes the initialization's well-formedness subject only to whether or not std::initializer_list is a literal type and the constructor being used is constexpr.



        Ultimately, it's all a bit murky.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Jan 17 at 7:52

























        answered Jan 17 at 7:21









        StoryTellerStoryTeller

        99.5k12201271




        99.5k12201271

























            5














            It appears std::initializer_list does not yet (in C++17) fulfill the requirements of literal type (which is a requirement the type of a constexpr variable has to satisfy).



            A discussion on whether it does so in C++14 is found in this post: Why isn't std::initializer_list defined as a literal type?
            which itself was a followup to the post discussing Is it legal to declare a constexpr initializer_list object?



            I compared the citations provided in the C++14 related post (of the C++14 standard) to the final working draft (of the C++17 standard) and they are the same.
            So there is no explicit requirement that std::initializer_list should be a literal type.



            Citations from the final working draft of C++17 (n4659):



            [basic.types]/10.5




            (10.5) a possibly cv-qualified class type (Clause 12) that has all of the
            following properties:

            (10.5.1) — it has a trivial destructor,

            (10.5.2) — it is either a closure type (8.1.5.1), an aggregate type (11.6.1),
            or has at least one constexpr constructor or constructor template
            (possibly inherited (10.3.3) from a base class) that is not a copy or
            move constructor,

            (10.5.3) — if it is a union, at least one of its non-static data members is of non-volatile literal type, and

            (10.5.4) — if it is not a union, all of its non-static data members and base
            classes are of non-volatile literal types.




            [initializer_list.syn]/1





            1. An object of type initializer_list provides access to an array of objects of type const E. [ Note:A pair of pointers or a pointer plus a length would be obvious representations for initializer_list. initializer_list is used to implement initializer lists as specified in 11.6.4. Copying an initializer list does not copy the underlying elements. —end note ]




            That is the reason why it is not legal to declare a constexpr initializer_list object.






            share|improve this answer


























            • Then how is it that the std::array, which takes an initializer_list constructor parameter, does satisfy being a constexpr? Why is it that the compiler can't similarly construct a backing array and an initializer_list to it as constexpr?

              – Miral
              Jan 17 at 22:35











            • I suppose technically that's aggregate initialisation, rather than actually using an initializer_list constructor. But it's still weird that this is treated differently.

              – Miral
              Jan 17 at 22:48
















            5














            It appears std::initializer_list does not yet (in C++17) fulfill the requirements of literal type (which is a requirement the type of a constexpr variable has to satisfy).



            A discussion on whether it does so in C++14 is found in this post: Why isn't std::initializer_list defined as a literal type?
            which itself was a followup to the post discussing Is it legal to declare a constexpr initializer_list object?



            I compared the citations provided in the C++14 related post (of the C++14 standard) to the final working draft (of the C++17 standard) and they are the same.
            So there is no explicit requirement that std::initializer_list should be a literal type.



            Citations from the final working draft of C++17 (n4659):



            [basic.types]/10.5




            (10.5) a possibly cv-qualified class type (Clause 12) that has all of the
            following properties:

            (10.5.1) — it has a trivial destructor,

            (10.5.2) — it is either a closure type (8.1.5.1), an aggregate type (11.6.1),
            or has at least one constexpr constructor or constructor template
            (possibly inherited (10.3.3) from a base class) that is not a copy or
            move constructor,

            (10.5.3) — if it is a union, at least one of its non-static data members is of non-volatile literal type, and

            (10.5.4) — if it is not a union, all of its non-static data members and base
            classes are of non-volatile literal types.




            [initializer_list.syn]/1





            1. An object of type initializer_list provides access to an array of objects of type const E. [ Note:A pair of pointers or a pointer plus a length would be obvious representations for initializer_list. initializer_list is used to implement initializer lists as specified in 11.6.4. Copying an initializer list does not copy the underlying elements. —end note ]




            That is the reason why it is not legal to declare a constexpr initializer_list object.






            share|improve this answer


























            • Then how is it that the std::array, which takes an initializer_list constructor parameter, does satisfy being a constexpr? Why is it that the compiler can't similarly construct a backing array and an initializer_list to it as constexpr?

              – Miral
              Jan 17 at 22:35











            • I suppose technically that's aggregate initialisation, rather than actually using an initializer_list constructor. But it's still weird that this is treated differently.

              – Miral
              Jan 17 at 22:48














            5












            5








            5







            It appears std::initializer_list does not yet (in C++17) fulfill the requirements of literal type (which is a requirement the type of a constexpr variable has to satisfy).



            A discussion on whether it does so in C++14 is found in this post: Why isn't std::initializer_list defined as a literal type?
            which itself was a followup to the post discussing Is it legal to declare a constexpr initializer_list object?



            I compared the citations provided in the C++14 related post (of the C++14 standard) to the final working draft (of the C++17 standard) and they are the same.
            So there is no explicit requirement that std::initializer_list should be a literal type.



            Citations from the final working draft of C++17 (n4659):



            [basic.types]/10.5




            (10.5) a possibly cv-qualified class type (Clause 12) that has all of the
            following properties:

            (10.5.1) — it has a trivial destructor,

            (10.5.2) — it is either a closure type (8.1.5.1), an aggregate type (11.6.1),
            or has at least one constexpr constructor or constructor template
            (possibly inherited (10.3.3) from a base class) that is not a copy or
            move constructor,

            (10.5.3) — if it is a union, at least one of its non-static data members is of non-volatile literal type, and

            (10.5.4) — if it is not a union, all of its non-static data members and base
            classes are of non-volatile literal types.




            [initializer_list.syn]/1





            1. An object of type initializer_list provides access to an array of objects of type const E. [ Note:A pair of pointers or a pointer plus a length would be obvious representations for initializer_list. initializer_list is used to implement initializer lists as specified in 11.6.4. Copying an initializer list does not copy the underlying elements. —end note ]




            That is the reason why it is not legal to declare a constexpr initializer_list object.






            share|improve this answer















            It appears std::initializer_list does not yet (in C++17) fulfill the requirements of literal type (which is a requirement the type of a constexpr variable has to satisfy).



            A discussion on whether it does so in C++14 is found in this post: Why isn't std::initializer_list defined as a literal type?
            which itself was a followup to the post discussing Is it legal to declare a constexpr initializer_list object?



            I compared the citations provided in the C++14 related post (of the C++14 standard) to the final working draft (of the C++17 standard) and they are the same.
            So there is no explicit requirement that std::initializer_list should be a literal type.



            Citations from the final working draft of C++17 (n4659):



            [basic.types]/10.5




            (10.5) a possibly cv-qualified class type (Clause 12) that has all of the
            following properties:

            (10.5.1) — it has a trivial destructor,

            (10.5.2) — it is either a closure type (8.1.5.1), an aggregate type (11.6.1),
            or has at least one constexpr constructor or constructor template
            (possibly inherited (10.3.3) from a base class) that is not a copy or
            move constructor,

            (10.5.3) — if it is a union, at least one of its non-static data members is of non-volatile literal type, and

            (10.5.4) — if it is not a union, all of its non-static data members and base
            classes are of non-volatile literal types.




            [initializer_list.syn]/1





            1. An object of type initializer_list provides access to an array of objects of type const E. [ Note:A pair of pointers or a pointer plus a length would be obvious representations for initializer_list. initializer_list is used to implement initializer lists as specified in 11.6.4. Copying an initializer list does not copy the underlying elements. —end note ]




            That is the reason why it is not legal to declare a constexpr initializer_list object.







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Jan 17 at 7:48

























            answered Jan 17 at 7:19









            P.WP.W

            14.9k31452




            14.9k31452













            • Then how is it that the std::array, which takes an initializer_list constructor parameter, does satisfy being a constexpr? Why is it that the compiler can't similarly construct a backing array and an initializer_list to it as constexpr?

              – Miral
              Jan 17 at 22:35











            • I suppose technically that's aggregate initialisation, rather than actually using an initializer_list constructor. But it's still weird that this is treated differently.

              – Miral
              Jan 17 at 22:48



















            • Then how is it that the std::array, which takes an initializer_list constructor parameter, does satisfy being a constexpr? Why is it that the compiler can't similarly construct a backing array and an initializer_list to it as constexpr?

              – Miral
              Jan 17 at 22:35











            • I suppose technically that's aggregate initialisation, rather than actually using an initializer_list constructor. But it's still weird that this is treated differently.

              – Miral
              Jan 17 at 22:48

















            Then how is it that the std::array, which takes an initializer_list constructor parameter, does satisfy being a constexpr? Why is it that the compiler can't similarly construct a backing array and an initializer_list to it as constexpr?

            – Miral
            Jan 17 at 22:35





            Then how is it that the std::array, which takes an initializer_list constructor parameter, does satisfy being a constexpr? Why is it that the compiler can't similarly construct a backing array and an initializer_list to it as constexpr?

            – Miral
            Jan 17 at 22:35













            I suppose technically that's aggregate initialisation, rather than actually using an initializer_list constructor. But it's still weird that this is treated differently.

            – Miral
            Jan 17 at 22:48





            I suppose technically that's aggregate initialisation, rather than actually using an initializer_list constructor. But it's still weird that this is treated differently.

            – Miral
            Jan 17 at 22:48


















            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54230229%2fwhy-is-an-initializer-list-of-enum-values-not-considered-a-constant-expression%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            MongoDB - Not Authorized To Execute Command

            in spring boot 2.1 many test slices are not allowed anymore due to multiple @BootstrapWith

            How to fix TextFormField cause rebuild widget in Flutter