Jeff L
Jeff L

Reputation: 643

Can't create multiple constexpr getter

I have a struct which wraps multiple std::sets 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

  1. Perform the set selection at compile time
  2. Access the sets with a mutable reference and with a const immutable reference

Upvotes: 0

Views: 99

Answers (1)

JeJo
JeJo

Reputation: 32952

What do I need to do to both be able to both

  1. Perform the set selection at compile time.
  2. Access the sets with a mutable reference and with a const immutable reference.

In 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{};
};

See example code


However, prior to [tag:C++:23], you need to

  1. either provide both const and non-const getter members,
  2. or move the common logic to a non member 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
}

See example 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!"

Upvotes: 2

Related Questions