Home > database >  Idiomatic way of handling const template arguments in function templates
Idiomatic way of handling const template arguments in function templates

Time:07-07

For a framework project I'm working on, I provide an interface for users to access objects through a handle-like interface--i.e. they do not own the objects themselves, but they need to use them.

The class the user cares about looks something like:

template <typename T>
class handle {
public:
  using value_type = std::remove_const_t<T>;
  using const_pointer = value_type const*;
  using const_reference = value_type const&;

  // Pointer-like API; const-only access
  const_pointer operator->() const noexcept { return ptr_; }
  const_reference operator*() const noexcept { return *ptr_; }

  // See explanation in post...
  template <typename U, typename V>
  friend bool operator==(handle<U> u, handle<V> v)
    requires std::same_as<typename handle<U>::value_type, 
                          typename handle<V>::value_type>;

  // Constructor and other member functions hidden.

private:
  const_pointer ptr_;
};

The handle is constructed by the framework, and the user has only const access to the object through the handle. For an int owned by the framework, it is therefore immaterial whether a user specifies (e.g.) handle<int> or handle<int const>, as the interface will be identical for each.

The question is, how do I simply create an equality function that supports potentially const-qualified template arguments. IOW, I need:

handle<int> hi{...};
handle<int const> hic{...};
assert(hi == hic);

The closest I've come up with is to create a free function template that has access to ptr_:

template <typename U, typename V>
bool operator==(handle<U> u, handle<V> v)
  requires std::same_as<typename handle<U>::value_type, typename handle<V>::value_type>
{
      return u.ptr_ == v.ptr_;
}

But this is a lot of boilerplate! Is there a better way to handle this problem using modern C ?

CodePudding user response:

You can just write a member comparison:

template <class T>
struct handle {
    using value_type = std::remove_const_t<T>;
    using const_pointer = value_type const*;

    template <class U>
    friend struct handle;

    template <class U>
        // this can be same_as<T const, U const>, etc.
        requires std::equality_comparable_with<T*, U*>
    bool operator==(handle<U> rhs) const {
        return ptr == rhs.ptr;
    }

private:
    const_pointer ptr;
};

With the new C 20 comparison rules, this is sufficient to give you both homogeneous and heterogeneous == and !=. Demo


A simpler approach would be:

template <class T>
struct handle {
    using value_type = T;
    using const_pointer = value_type const*;

    friend bool operator==(handle, handle) = default;

private:
    const_pointer ptr;
};

template <class T> struct handle<T const> : handle<T> { };

Here, handle<int const> and handle<int> are separate types (even if handle<int const> is silly), but handle<int const> is a handle<int> so the inherited comparison just works. Demo.

  • Related