0

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
3
  • 2
    member functions can be overloaded on the const specifier. have a const and a non const overload of get Commented Apr 8, 2024 at 23:43
  • Can't you use std::tuple here? e.g. godbolt.org/z/h3cP83o5d Commented Apr 8, 2024 at 23:48
  • As an aside, your static_assert() can be reduced, since we know i == 0 || i == 1 is false. Commented Apr 14, 2024 at 8:20

1 Answer 1

2

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.