I have a struct which wraps multiple std::set
s of data. I want access to the sets to be computed at compile time. To do this, I created a template constexpr
getter method to return the requested set.
// simplified sample
struct TwoSets
{
template <int i> constexpr std::set<int> &get() noexcept
{
if constexpr (i == 0) {
return first;
} else if constexpr (i == 1) {
return second;
} else {
static_assert(i == 0 || i == 1, "invalid");
}
}
std::set<int> first;
std::set<int> second;
};
This works, but there are parts of the code that insert to a given set, and parts of the code that want read-only access to a set via a const
reference to the set like so:
TwoSets sets;
sets.get<0>().insert(0);
// ...elsewhere in code
const TwoSets &const_sets = sets;
std::cout << const_sets.get<0>().size();
This results in an error:
error: passing ‘const TwoSets’ as ‘this’ argument discards qualifiers [-fpermissive]
This can be fixed by marking get
as const
/returning a const
reference, which breaks the insertion code. What do I need to do to both be able to both
const
immutable referenceWhat do I need to do to both be able to both
- Perform the set selection at compile time.
- Access the sets with a mutable reference and with a
const
immutable reference.
In c++23 deducing this
is the perfect solution for such scenarios.
struct TwoSets
{
template <int i, typename Self>
constexpr auto&& get(this Self&& self ) noexcept
{
if constexpr (i == 0) {
return std::forward<Self>(self).first;
}
else if constexpr (i == 1) {
return std::forward<Self>(self).second;
}
else {
static_assert(i == 0 || i == 1, "invalid");
}
}
std::set<int> first{};
std::set<int> second{};
};
However, prior to [tag:C++:23], you need to
const
and non-const
getter members,friend
or an internal function.moving the common logic to a function would look like:
// Forward declaration
struct TwoSets;
// some internal namespace
template<int i, typename T> constexpr auto& getSet(T&&) noexcept;
struct TwoSets
{
template <int i>
constexpr std::set<int>& get() noexcept {
return getSet<i>(*this);
}
template <int i>
constexpr const std::set<int>& get() const noexcept {
return getSet<i>(*this);
}
std::set<int> first;
std::set<int> second;
};
// some internal namespace
// Friend function to get the set from TwoSets
template<int i, typename T>
constexpr auto& getSet(T&& sets) noexcept
{
// ... code
}
Side note: If your actual scenario is like shown, you should consider std::tuple
as @paddy suggested in the comment, and Keep It Simple, & Stupid!"