9

In the following if I use constexpr the compiler apparently the compiler says "expression must have a constant value", this happens on MSVC and GCC:

int main() {
    constexpr auto nnn = {
        "this", "sentence", "is", "not", "a", "sentence",
            "this", "sentence", "is", "a", "hoax"
    };
}

Without the constexpr the compiler can deduce that nnn variable is an std::initializer_list<const char*>. And the std::initialiser_list class has a constexpr constructor, why can't this be constexpr?

14
  • 2
    This question is similar to: Why do auto and template type deduction differ for braced initializers?. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem.
    – Oersted
    Commented Sep 19 at 8:01
  • May be to complete the dup, the braced initialization leads to deduce a std::initializer_list which cannot be used in a constant expression. Sorry, I don't have right now the precise statements from the standard (but the compiler are quite explicit about that :) ). I think it might be the lack of a constexpr copy/move constructor (nn deduced as std::initializer_list and copy/move constructed from the right hand side)
    – Oersted
    Commented Sep 19 at 8:07
  • Clang error message is more informative: "note: pointer to subobject of temporary is not a constant expression" Demo
    – Jarod42
    Commented Sep 19 at 8:08
  • btw I removed the dupe flag, cause it does not address the constexpr part: stackoverflow.com/questions/17582667/…
    – Oersted
    Commented Sep 19 at 8:08
  • 2
    @user12002570: not when it is used in local scope Demo
    – Jarod42
    Commented Sep 19 at 8:24

2 Answers 2

14

Type deduction is not failing. The compiler is deducing auto to std::initializer_list<const char*>. You get the same result if you wrote std::initializer_list<const char*> instead of auto.

The initialization fails to be a constant expression (something that can only be checked after all types have been deduced).

It fails to be a constant expression, because std::initializer_list works as if an array with the same storage duration was defined in the same scope to hold the elements and as if the std::initializer_list object held pointers to the beginning and end of that array.

Because that array would have automatic storage duration in your example, the initialization of the std::initializer_list object can't be a constant expression, because the result of a constant expression can only contain pointers to objects with static storage duration. (The value of pointers to an object with automatic storage duration would change each time the initialization is performed, so can't be constant expression results.)

You can force the unnamed array to have static storage duration (as well as the std::initializer_list) by using static:

int main() {
    constexpr static auto nnn = {
        "this", "sentence", "is", "not", "a", "sentence",
            "this", "sentence", "is", "a", "hoax"
    };
}
12
  • Do you have standard quotes for the "as if" part? Commented Sep 19 at 9:03
  • 2
    @WeijunZhou eel.is/c++draft/dcl.init#list-5 and eel.is/c++draft/dcl.init#list-6. It is not exactly the same as described in my answer, since I wanted to avoid talking about temporary objects, but the effect is the same. Commented Sep 19 at 9:05
  • 1
    @WeijunZhou And arguably "and the std​::​initializer_list<E> object is constructed to refer to that array" should somehow be covered in [expr.const] which it currently isn't. Commented Sep 19 at 9:06
  • All the initializer_list would have to do is contain an array of const char pointers, each of which is pointing to static memory valid for the life of the program. The initialiser_list itself would have automatic storage duration, true, but that would be the case for basically any class with a constexpr constructor, and those can be assigned to constexpr without making it static. It doesn't make sense.
    – Zebrafish
    Commented Sep 19 at 14:23
  • @Zebrafish It could, but that is not specified. It is specified that the array has the same lifetime as the initializer_list object. For example, (assuming you were permitted to) if you called main recursively, the standard guarantees you that nnn.data() in each recursive call will be a different pointer value. Your suggestion also wouldn't work generally in the non-constexpr case, because the initializers are supposed to be able to depend on runtime values, which may be different in each call. Commented Sep 19 at 15:12
-1

The constexpr constructor mentioned by OP is the default constructor that creats an empty list. The actual usable constructor doesn't have any standard interface to begin with. One primary requirement of constant expressions is that every invoked function is visible, inlined and sepcified as constexpr. As a result, std::initializer_list just cannot be constexpr. It looked like a necessary abomination when C++11 was about to be completed. I wished it were deprecated by now. I can not imagine any use case without alternatives. Usage as function/constructor argument can be handled with std::span<const T> it just requires a bit more typing:

constexpr auto foo(std::span<std::uint64_t> arr){
   return std::ranges::fold_left(arr,0ull,std::plus<>{});
};

auto constexpr N = foo(std::array{1ull, 2, 3});

Other usages are just duplicates of std::array:

constexpr std::array nnn = {
        "this"sv, "sentence", "is", "not", "a", "sentence",
            "this", "sentence", "is", "a", "hoax"
    };//fixed-size array of std::string_views

As of C++23, all std containers have range constructors, So it's just possible to pass in a std::array:

std::vector<int> vec(std::from_range, std::array{1,2,3,4});

IMO, std::initializer_list has no more practical use cases. Everything already has a better substitution.

10
  • 1
    "std::initializer_list just cannot be constexpr" It is allowed by compilers by adding static as local scope, or just at global scope...
    – Jarod42
    Commented Sep 19 at 12:15
  • @Jarod42 If it's marked with constexpr why can't it be constexpr? That's so confusing. I know that constexpr doesn't guarantee constexpr, but consteval does, however to append the keyword constexpr to a type that "can't" be constexpr is really confusing.
    – Zebrafish
    Commented Sep 19 at 14:33
  • @Zebrafish: Unsure why I'm been mentioned (@). My comment was to spot issue in the answer.
    – Jarod42
    Commented Sep 19 at 14:51
  • The none-default constructor used to create an actual list is not constexpr. Because it doesn't have an inline definition. It's platform specific constructor with out of line binary definition.
    – Red.Wave
    Commented Sep 19 at 16:32
  • If anyone wants a citation on standard wording of exact reason why initializer_list can not be constexpr, tag the question as language_lawyer. But the gist of it is first paragraph in my post, unless something changes in future std revisions.
    – Red.Wave
    Commented Sep 19 at 20:49

Not the answer you're looking for? Browse other questions tagged or ask your own question.