c++ - Calling the non-variadic constructor in a class template without casting

72 Views Asked by At

Given class template cArray with two constructors:

  1. one that takes a single std::size parameter, and
  2. one that takes any number of arguments (provided they are constructible from T).

Question

How do I modify cArray so that it uses the first (single-argument) constructor when given a single integer literal without casting to std::size_t?

  • What is the takeaway here (from a conceptual standpoint)?

Example:

  • cArray<int> arr1(7): calls the variadic constructor, resulting in a cArray<int> containing one element (7).

  • cArray<int> arr1((std::size_t)7): behaves correctly (but always casting is inconvenient).

Code

godbolt

cArray.hpp

template <typename T>
class cArrayIterator;

template <typename T>
class cArray
{
public:
    using value_type      = T;
    using array_type      = cArray<T>;
    using size_type       = std::size_t;
    using difference_type = std::ptrdiff_t;
    using reference       = value_type&;
    using const_reference = value_type const&;
    using pointer         = value_type*;
    using const_pointer   = value_type const*;
    using iterator        = cArrayIterator<T>;
    using const_iterator  = cArrayIterator<const T>;

    cArray(std::size_t _size)
    {
        if (_size > 0)
        {
            m_size = _size;
            m_arr  = new T[_size];
        }
        else
        {
            throw std::runtime_error("TO DO: array length cannot be 0!");
        }
    }

    template <typename... Elements>
        requires(std::constructible_from<T, Elements>&&...)
    cArray(Elements&&... es)
    : m_size{ sizeof...(es) }
    , m_arr{ new T[sizeof...(es)] { es... } }
    {}

    // special member functions
    cArray(const cArray& _other)
    {
        m_size = _other.size();
        m_arr  = new T[m_size];

        std::copy_n(_other.m_arr, m_size, m_arr);
    }

    cArray(cArray&& _other) noexcept
    : m_size(std::exchange(_other.m_size, 0))
    , m_arr(std::exchange(_other.m_arr, nullptr))
    {}

    ~cArray()
    {
        delete[] m_arr;
    }

    cArray& operator=(const cArray& _other)
    {
        delete[] m_arr;
        m_size = _other.m_size;
        m_arr  = new T[m_size];

        std::copy_n(_other.m_arr, m_size, m_arr);

        return *this;
    }

    cArray& operator=(cArray&& _other) noexcept
    {
        delete[] m_arr;
        m_size = std::exchange(_other.m_size, 0);
        m_arr  = std::exchange(_other.m_arr, nullptr);

        return *this;
    }

    constexpr value_type& at(const size_type _index)
    {
        if (_index < m_size)
        {
            return m_arr[_index];
        }
        else
        {
            throw std::out_of_range("TO DO: index out of bounds."); // TO DO: custom exception
        }
    }

    constexpr const_reference back() const noexcept
    {
        return m_arr[m_size - 1];
    }

    constexpr const_reference front() const noexcept
    {
        return m_arr[(size_type)0];
    }

    iterator begin()
    {
        return iterator(*this, 0);
    }

    iterator end()
    {
        return iterator(*this, m_size);
    }

    const_iterator begin() const
    {
        return const_iterator(*this, 0);
    }

    const_iterator end() const
    {
        return const_iterator(*this, m_size);
    }

    constexpr size_type size() const noexcept
    {
        return m_size;
    }

    constexpr T& operator[](const size_type _pos)
    {
        return m_arr[_pos];
    }

    constexpr const T& operator[](const size_type _pos) const
    {
        return m_arr[_pos];
    }

private:
    friend cArrayIterator<T>;
    friend cArrayIterator<T const>;

    std::size_t  m_size;
    T* m_arr;
};

template<class T>
cArray(const T&, const T&) -> cArray<T&>;

template<class T>
explicit cArray(T&&, T&&)->cArray<T>;

template <typename T>
class cArrayIterator
{
    using array_type = std::conditional_t<std::is_const_v<T>, const cArray<std::remove_const_t<T>>, cArray<T>>;

public:
    using value_type          = std::remove_const_t<T>;;
    using value_const_type    = std::add_const_t<T>;
    using iterator_type       = cArrayIterator<T>;
    using iterator_const_type = cArrayIterator<const T>;
    using size_type           = std::size_t;
    using difference_type     = std::ptrdiff_t;
    using reference           = value_type&;
    using const_reference     = value_type const&;
    using pointer             = value_type*;
    using const_pointer       = const value_type*;
    using iterator_category   = std::random_access_iterator_tag;

    // constructor(s)
    explicit cArrayIterator(array_type& _array, size_type const _index)
    : m_array_ref(_array)
    , m_index(_index)
    {}

    auto operator<=>(const cArrayIterator&) const = default;

    iterator_type& operator=(const cArrayIterator& _other)
    {
        m_array_ref = _other.m_array_ref.get();
        m_index     = _other.m_index;

        return *this;
    }

    iterator_type& operator=(cArrayIterator&& _other) noexcept
    {
        // Copy the data pointer and its length from the
        // source object.
        m_array_ref = _other.m_array_ref;
        m_index     = _other.m_index;

        // Release the data pointer from the source object so that
        // the destructor does not free the memory multiple times.
        _other._data = nullptr;
        _other._length = 0;

        m_array_ref = std::move(_other.m_array_ref);
        m_index = _other.m_index;

        return *this;
    }

    bool operator==(iterator_type const& _other) const
    {
        return ((m_array_ref.get().m_arr == _other.m_array_ref.get().m_arr) &&
            (m_index == _other.m_index));
    }

    bool operator!=(iterator_type const& _other) const
    {
        return ((m_array_ref.get().m_arr != _other.m_array_ref.get().m_arr) ||
                (m_index != _other.m_index));
    }

    const_reference operator*() const
    {
        // make sure array is within bounds
        if ((m_index < 0) || (m_index >= m_array_ref.get().m_size))
        {
            throw std::logic_error("TO DO: Cannot dereferentiate the iterator");
        }

        return m_array_ref.get().m_arr[m_index];
    }

    pointer operator->() const
    {
        // make sure array is within bounds
        if ((m_index < 0) || (m_index >= m_array_ref.get().m_size))
        {
            throw std::logic_error("TO DO: Cannot dereferentiate the iterator");
        }

        return &(m_array_ref.get().m_arr[m_index]);
    }

    iterator_type& operator++()
    {
        if (m_index >= m_array_ref.get().m_size)
        {
            throw std::out_of_range("TO DO: index out of bounds!");
        }

        m_index++;

        return *this;
    }

    iterator_type operator++(int)
    {
        iterator_type temp = *this;

        ++* this;

        return temp;
    }

    iterator_type& operator--()
    {
        if (m_index <= 0)
        {
            throw std::out_of_range("TO DO: index out of bounds!");
        }

        m_index--;

        return *this;
    }

    iterator_type operator--(int)
    {
        iterator_type temp = *this;

        --* this;

        return temp;
    }

    iterator_type operator+(difference_type _other) const
    {
        iterator_type temp = *this;

        return temp += _other;
    }

    difference_type operator+(iterator_type const& _other) const
    {
        return m_index + _other.m_index;
    }

    iterator_type operator-(difference_type _offset) const
    {
        iterator_type temp = *this;

        return temp -= _offset;
    }

    difference_type operator-(iterator_type const& _other) const
    {
        return m_index - _other.m_index;
    }

    iterator_type& operator+=(const difference_type _offset)
    {
        difference_type next = m_index + _offset;

        if (next >= m_array_ref.get().m_size)
        {
            throw std::out_of_range("TO DO: Iterator cannot be incremented past the bounds of the range");
        }

        m_index = next;

        return *this;
    }

    iterator_type& operator-=(const difference_type _offset)
    {
        difference_type next = m_index - _offset;

        if (next > 0)
        {
            throw std::out_of_range("TO DO: Iterator cannot be incremented past the bounds of the range");
        }

        m_index = next;

        return *this;
    }

    value_type& operator[](size_type _index) const
    {
        if (_index >= m_array_ref.get().m_size)
        {
            throw std::out_of_range("TO DO: index out of range.");
        }

        return (m_array_ref.get().m_arr + _index);
    }

private:
    std::reference_wrapper<array_type> m_array_ref;
    size_type                          m_index = 0;
};

cArrayImpl.cpp

template class DArray<int>;
template class DArray<std::string>;

main.cpp

#include <iostream>
#include <string>
#include ".\cArray.hpp"
#include ".\cArray_tests.cpp"
#include ".\cArrayImpl.cpp"

int
main(int argc, char ** argv)
{

        cArray<int> arr1((std::size_t)10);
        std::cout << "cArray<int> WITH cast to std::size    (expected 10): " << arr1.size() << std::endl;

        cArray<int> arr2(10);
        std::cout << "cArray<int> WITHOUT cast to std::size (expected 10): " << arr2.size() << std::endl;

        //cArray<double> arr3(10);
        std::cout << "cArray<double> WITHOUT cast = ERROR" << std::endl;
}

I tried deduction guides, and read about 10 questions here on SO, but I was unable

1

There are 1 best solutions below

0
On BEST ANSWER

You might add more restriction to your variadic constructor:

You want to remove the overload when sizeof...(Elements) == 1 && (std::convertible_to<Elements, std::size_t> && ...).

so

template <typename... Elements>
requires((std::constructible_from<T, Elements> && ...)
        && (sizeof...(Elements) != 1
            || !(std::convertible_to<Elements, std::size_t> && ...)))
cArray(Elements&&... es)
    : m_size{ sizeof...(es) }
    , m_arr{ new T[sizeof...(es)] { es... } }
{}

Demo

You might also want to convert type convertible (both) to CArray and Elements.

Providing tagged/named constructors are good alternative.