I am developing a C++17 framework that has optional third-party dependencies and I'd like to construct an std::array
that contains the names of the available dependencies.
The CMake file sets preprocessor definitions such as HAS_DEPENDENCY1
, HAS_DEPENDENCY2
, ..., so writing such a function with an std::vector is quite easy:
using literal = const char*;
std::vector<literal> available_dependencies() {
std::vector<literal> dependencies{};
#ifdef HAS_DEPENDENCY1
dependencies.emplace_back("dependency 1");
#endif
#ifdef HAS_DEPENDENCY2
dependencies.emplace_back("dependency 2");
#endif
return dependencies;
}
I'd like to have a compile-time, constexpr equivalent of that. I tried this:
constexpr static std::array available_dependencies{
#ifdef HAS_DEPENDENCY1
"dependency 1",
#endif
#ifdef HAS_DEPENDENCY2
"dependency 2"
#endif
};
but there are two issues:
std::array
(the array is empty).I managed to address the first issue with something like:
template <typename... Literals>
constexpr std::array<literal, sizeof...(Literals)> make_literal_array(Literals&&... literals) {
return {literals...};
}
but this doesn't support trailing commas. I tried with an std::initializer_list
(they allow trailing commas), but couldn't get it to work.
Perhaps you'll be more inspired than me?
Here is another proposal that uses the fact that initializer lists can cope with trailing commas. Since it is uneasy to directly use std::array
in your context, why not defining a std::initializer_list
instead (setting the type but not the number of items)
constexpr static std::initializer_list<const char*> available_dependencies_list = {
#ifdef HAS_DEPENDENCY1
"dependency 1",
#endif
#ifdef HAS_DEPENDENCY2
"dependency 2",
#endif
};
You can iterate it like in for-range loop
for (auto x : available_dependencies_list) {
std::cout << x << "\n";
}
If you want to have a std::array
, you can have one with the following
template<int N,typename T>
constexpr auto as_array (std::initializer_list<T> l) {
std::array<T,N> res {};
std::size_t i=0;
for (auto x : l) { res[i++] = x; }
return res;
}
which can be used e.g.
constexpr auto available_dependencies = as_array <available_dependencies_list.size()> (available_dependencies_list);
The ugly fact here is that one has to provide the size as a template parameter (but in c++17
we can't do the same for the list itself), so one has some kind of redundancy. We can still (shamelessly) use a macro such as
#define AS_ARRAY(l) as_array<l.size()>(l);
constexpr auto available_dependencies = AS_ARRAY(available_dependencies_list);
Update
According to DavidG's comment, there was a useless std::make_index_sequence<N>()
in the answer, so I updated the answer above which makes the code simpler.
Moreover, I have observed an issue with clang
when using const char*
(no issue with g++
and msvc
though), e.g.
error: static assertion expression is not an integral constant expression
static_assert (available_dependencies[0] == "dependency 1");
Using std::string_view
instead of const char*
for the template parameter of the std::initializer_list
makes it work for the 3 compilers