Reputation: 10972
Actually this "problem" feels extremely simple. While doing some calculated icon offsets, I came up with the following approach:
namespace Icons {
struct IconSet {
constexpr IconSet(size_t base_offset) noexcept
: base_offset_(base_offset), icon(base_offset * 3), iconSmall(icon + 1), iconBig(icon + 2) {
}
size_t icon;
size_t iconSmall;
size_t iconBig;
size_t base_offset_;
constexpr size_t next() const {
return base_offset_ + 1;
}
};
static constexpr IconSet flower = IconSet(0);
static constexpr IconSet tree = IconSet(flower.next());
static constexpr IconSet forest = IconSet(tree.next());
static constexpr IconSet mountain = IconSet(forest.next());
}
Now one may write Icons::tree.iconBig
for example to get that icon's calculated offset. Basically the designer can change the icons - sometimes adding/removing as well - but always has to provide the entire set (normal, small and big) by convention.
As you see, the issue with this approach is that I had to do that next()
function and use it repeatedly - a normal enum wouldn't have this downside.
I know of BOOST_PP and other macro tricks, but I was hoping for something without macro's - since I have a feeling it is not needed and I then sort of would prefer what I already have with the plain next()
function.
Another solution would of course just be a normal enum and a calculation function, but that is defeating the purpose of laying it out precalculated.
Hence, I am looking for a simple and portable solution that will give that enum-like functionality. It doesn't have to be compile time or constexpr
if for example just inline will make it easier.
Upvotes: 10
Views: 704
Reputation: 435
Here is an approach you can use, based on a fold expression over a compile time integer sequence to instantiate the icons by index. The structured binding gets you individually named non-static, non-constexpr variables.
The anonymous namespace inside Icons
makes those definitions visible in this translation unit only, which you may or may not want.
Compiler explorer link so you can explore the code options yourself.
#include <cstddef>
#include <array>
#include <utility>
namespace Icons {
struct IconSet {
constexpr IconSet(size_t base_offset) noexcept
: base_offset_(base_offset), icon(base_offset * 3), iconSmall(icon + 1), iconBig(icon + 2) {
}
size_t icon;
size_t iconSmall;
size_t iconBig;
size_t base_offset_;
};
template <std::size_t... Ints>
constexpr auto make_icons_helper(std::index_sequence<Ints...>) -> std::array<IconSet, sizeof...(Ints)>
{
return {IconSet(Ints)...};
}
template <size_t N>
constexpr auto make_icons()
{
return make_icons_helper(std::make_index_sequence<N>{});
}
namespace {
auto [flower, tree, forest, mountain] = make_icons<4>();
}
}
int main()
{
return Icons::forest.iconSmall;
}
Upvotes: 9
Reputation: 63124
A simple, non-constexpr solution using a static counter and relying on the fact that static initialization is performed top-to-bottom within a single TU:
namespace Icons {
namespace detail_iconSet {
static std::size_t current_base_offset = 0;
}
struct IconSet {
IconSet() noexcept
: base_offset_(detail_iconSet::current_base_offset++)
, icon(base_offset_ * 3)
, iconSmall(icon + 1)
, iconBig(icon + 2) { }
std::size_t base_offset_;
std::size_t icon;
std::size_t iconSmall;
std::size_t iconBig;
};
static IconSet flower;
static IconSet tree;
static IconSet forest;
static IconSet mountain;
}
The catch is that this will behave weirdly if you have several headers containing IconSet
definitions (i.e. their numbering will change depending on the order of inclusion), but then I don't think there's a way to avoid that.
Upvotes: 4