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, std::size_t...Is>
constexpr auto as_array_impl (std::initializer_list<T> l, std::index_sequence<Is...>) {
std::array<T,N> res {};
std::size_t i=0;
for (auto x : l) { res[i++] = x; }
return res;
}
template<int N,typename T>
constexpr auto as_array (std::initializer_list<T> l) {
return as_array_impl<N,T> (l, std::make_index_sequence<N>());
}
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);