Этот трюк существует в двух вариантах:
Массив заполняется сразу при запуске программы. Туда попадают элементы из всех единиц трансляции. Такой список, очевидно, не может constexpr
, потому что каждая единица трансляции компилируется независимо.
Массив заполняется во время компиляции. Тут он спокойно может быть constexpr
, но видит элементы только из текущей единицы трансляции, и все элементы должны быть зарегистрированы выше по тексту.
Вариант (1) в целом уже написан в другом ответе, но приведу немного другое исполнение, которое мне больше нравится - без замены функции на шаблонную переменную с operator()
и т.п.:
#include <iostream>
#include <set>
#include <type_traits>
namespace impl
{
std::set<int> &MutRegistry()
{
// Переменная обязательно внутри функции, чтобы избежать static init order fiasco.
static std::set<int> ret;
return ret;
}
template <int N>
static const auto register_value = []{
MutRegistry().insert(N);
return nullptr;
}();
}
const std::set<int> &Registry()
{
return impl::MutRegistry();
}
template <int N>
void foo()
{
(void)std::integral_constant<const std::nullptr_t *, &impl::register_value<N>>{};
}
int main()
{
for (int x : Registry())
std::cout << x << '\n'; // 10, 20
foo<10>();
foo<20>();
}
Вариант (2) пишется сильно сложнее, нужно использовать stateful metaprogramming.
#include <array>
#include <cstddef>
#include <iostream>
#include <type_traits>
#include <utility>
template <typename T>
struct tag {using type = T;};
template <auto V>
struct value_tag {static constexpr auto value = V;};
template <typename...>
struct type_list {};
namespace StatefulList
{
namespace // Hopefully guard against ODR violations?
{
namespace impl
{
template <typename Name, std::size_t Index>
struct ElemReader
{
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-template-friend"
#endif
friend constexpr auto adl_ListElem(ElemReader<Name, Index>);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
};
template <typename Name, std::size_t Index, typename Value>
struct ElemWriter
{
friend constexpr auto adl_ListElem(ElemReader<Name, Index>)
{
return tag<Value>{};
}
};
[[maybe_unused]] constexpr void adl_ListElem() {} // A dummy ADL target.
template <typename Name, std::size_t Index, typename Unique, typename = void>
struct CalcSize : std::integral_constant<std::size_t, Index> {};
template <typename Name, std::size_t Index, typename Unique>
struct CalcSize<Name, Index, Unique, decltype(void(adl_ListElem(ElemReader<Name, Index>{})))> : CalcSize<Name, Index + 1, Unique> {};
template <typename Name, std::size_t Index, typename Unique>
using ReadElem = typename decltype(adl_ListElem(ElemReader<Name, Index>{}))::type;
template <typename Name, typename I, typename Unique>
struct ReadElemList {};
template <typename Name, std::size_t ...I, typename Unique>
struct ReadElemList<Name, std::index_sequence<I...>, Unique> {using type = type_list<ReadElem<Name, I, Unique>...>;};
}
struct DefaultUnique {};
template <typename T>
struct DefaultPushBackUnique {};
// Calculates the current list size.
template <typename Name, typename Unique = DefaultUnique>
[[maybe_unused]] inline constexpr std::size_t size = impl::CalcSize<Name, 0, Unique>::value;
// Touch this type to append `Value` to the list.
template <typename Name, typename Value, typename Unique = DefaultPushBackUnique<Value>>
using PushBack = impl::ElemWriter<Name, size<Name, Unique>, Value>;
// Returns the type previously passed to `WriteState`, or causes a SFINAE error.
template <typename Name, std::size_t I, typename Unique = DefaultUnique>
using Elem = impl::ReadElem<Name, I, Unique>;
// Returns the list elements as `Meta::TypeList<...>`.
template <typename Name, typename Unique = DefaultUnique>
using Elems = typename impl::ReadElemList<Name, std::make_index_sequence<size<Name, Unique>>, Unique>::type;
}
}
struct FooList {};
namespace // Guard against ODR violations?
{
template <typename Unique = void>
consteval auto FooElems()
{
return []<typename ...P>(type_list<P...>){
return std::array<int, StatefulList::size<FooList, Unique>>{
P::value...
};
}(StatefulList::Elems<FooList, Unique>{});
}
}
template <int N>
auto foo() -> decltype(void(StatefulList::PushBack<FooList, value_tag<N>>{}))
{
}
int main()
{
if (false) // Не мешает.
{
foo<10>();
foo<20>();
}
for (int x : FooElems())
std::cout << x << '\n'; // 10, 20
foo<30>();
for (int x : FooElems())
std::cout << x << '\n'; // 10, 20 - wrong! Cached from the previous call.
struct UniqueType {};
for (int x : FooElems<UniqueType>())
std::cout << x << '\n'; // 10, 20, 30 - correct
}