I wanted to figure this out, because I was running some unit tests and our system allows you to mark the test with one or more initializer lists, which will translate to multiple runs with different arguments.
So I'd have something like:
constexpr std::initializer_list<bool> bool_options{ true, false };
// some macro
DEFINE_TEST_CASE(bool_options, [](bool current_option) { ... });
In practice, this was mostly done with some enum flags. In some cases, I needed an unset option, so I did this:
template <typename T>
using opt_initializer_list = std::initializer_list<std::optional<T>>;
constexpr opt_initializer_list bool_or_unset{ true, false, std::nullopt };
Again, also happened with enums. So the question I pondered, if one test used only valid values, and other was also supposed to be run with the setting unset (did not actually happen), could I write a function to convert std::initializer_list<bool> to std::initializer_list<std::optional<bool>> and add std::nullopt?
I tried this:
#include <initializer_list>
#include <optional>
// for debug
#include <iostream>
template <typename T>
using opt_initializer_list = std::initializer_list<std::optional<T>>;
template <typename TValue>
constexpr auto make_optional_initializer_list(std::initializer_list<TValue> vals)
{
return opt_initializer_list<TValue>{ vals, std::nullopt};
}
constexpr std::initializer_list<bool> bool_options{true, false};
// should evaluate to { true, false, std::nullopt }
constexpr opt_initializer_list<bool> bool_or_null_options = make_optional_initializer_list(bool_options);
The above will not compile, since { vals, std::nullopt} tries to invoke the begin/end iterator based constructor with invalid arguments.
But I think once you convert { ... } to an actual std::initializer_list instance, you cannot copy it in compile time. I have noticed however, that I can do this:
constexpr auto bool_options_notype = {true, false};
I am not sure what is deduced there, but if it does not count as std::initializer_list yet, but can be cast to one later, that would work - I don't need to work with initializer lists, just be able to cast the result implicitly to one.
std::initializer_listas a type has one job: to be an intermediary tool that allows one to use a braced-init-list ({}) to initialize an object with an array. Therefore, everyinitializer_listwill always contain the elements of a braced-init-list. You cannot manipulate them. You cannot take aninitializer_listand make a new one with more or fewer elements. You cannot convert some object type into aninitializer_list.It is a runtime intermediary for a compile-time grammatical construct, and the system is designed such that the only way to make one is to use that grammatical construct. And this is a one-way conversion.
And, even if you could create one, you couldn't return such a thing. The implicit array backing the
initializer_listis an automatic object, and it will be destroyed in the scope where the{}was used to create it. Thus, returning it would be returning a reference to a no-longer-existing array.What you want is a
std::arrayinstead. You can manipulate that as you like at compile-time. Of course, you will have to use iterator-based methods to initialize a container or whatever with that array.