4

I'm working on a custom array class in C++ (as a self-led exercise), and I'm not sure how to create a constructor that allows me to do something along the lines of:

#include "array.h"
#include <iostream>
int main()
{
    array<int> test = {1, 2, 3, 4};
    std::cout << test(1) << std::endl;
    return 0;
}

The error that the compiler (VS Express 2013) gives me is "no instance of constructor array::array [with T = int]" matches the argument list. argument types are (int, int, int, int)."

I'm not sure what the constructor that takes the enumeration of a set of elements is called. I know I've properly overloaded operator()(const int&). I also know that this (for a reason that's not clear to me) works:

#include "array.h"
#include <iostream>
int main()
{
    array<char> test = "abcd";
    std::cout << test(1) << std:: endl; // prints 'a', as expected. 
    std::cout << test(4) << std::endl; // prints 'd', as expected.
    return 0;
}

This is achieved with a array(const T[]) constructor: will the solution for the array<int> test = {1, 2, 3, ..., n} case be similar?

Thanks in advance for any guidance.

EDIT: Including the code below, in case it's helpful.

template<typename T>
class array
{
public:
    typedef T* iterator;
    typedef const T* const_iterator;
private:
    iterator head;
    unsigned long elems;
public:
    array()
        : head(nullptr)
        , elems(0) {}
    array(const unsigned long &size)
        : head(size > 0 ? new T[size] : nullptr)
        , elems(size) {}
    array(const T[]);
    array(const array&);
    ~array() { delete[] head; }

    iterator begin() const { return head; }
    iterator end() const { return head != nullptr ? &head[elems] : nullptr; }
    unsigned long size() const { return elems; }

    array& operator=(const array&);
    T& operator()(const unsigned long&);
};

template<typename T>
array<T>::array(const T rhs[])
{
    unsigned long size = sizeof(rhs) / sizeof(T);
    head = new T[size];
    iterator pointer = begin();
    for (const_iterator i = &rhs[0]; i != &rhs[0] + size; i++)
        *pointer++ = *i;
}

template<typename T>
array<T>::array(const array<T> &rhs)
{
    head = new T[rhs.size()];
    iterator pointer = begin();
    for (const_iterator i = rhs.begin(); i != rhs.end(); i++)
        *pointer++ = *i;
}

template<typename T>
array<T>& array<T>::operator=(const array<T> &rhs)
{
    if (this != &rhs)
    {
        delete[] head;
        head = new T[rhs.size()];
        iterator pointer = begin();
        for (const_iterator i = rhs.begin(); i != rhs.end(); i++)
            *pointer++ = *i;
    }
    return *this;
}

template<typename T>
T& array<T>::operator()(const unsigned long &index)
{
    if (index < 1 || index > size())
    {
        // Add some error-handling here.
    }
    return head[index - 1];
}
4
  • where is your array class template code? Commented Oct 15, 2014 at 20:43
  • That was a glaring omission on my part: apologies. I've included it above. Commented Oct 15, 2014 at 20:45
  • sizeof(rhs) in array<T>::array(const T rhs[]) certainly isn't doing what you think it is. Commented Oct 15, 2014 at 20:48
  • warning: sizeof on array function parameter will return size of 'const char *' instead of 'const char []' [-Wsizeof-array-argument]. Commented Oct 15, 2014 at 20:49

1 Answer 1

4
#include <initializer_list>

// ...

template <typename T>
class array
{   
    // ... 
    array(std::initializer_list<T> il);    

// ...    
template <typename T>
array<T>::array(std::initializer_list<T> il)
{
    unsigned long size = il.size();
    head = new T[size];
    iterator pointer = begin();
    for (const T& i : il)
        *pointer++ = i;
}

// ...
array<int> test = {1, 2, 3, 4};

DEMO


Suggested improvements:

  1. array(const T rhs[]); is an equivalent of array(const T* rhs);, that is, a pointer, which means that sizeof(rhs) / sizeof(T) expression will not give you the number of items. If you want a special constructor for const char*, then consider either an entire array<char> specialization or at least disabling this constructor from the overload resolution if T is not char

  2. head = new T[size]; default-initializes all elements (calls default constructor for each instance of type T). Then you call an assignment operation: *pointer++ = *i;. This can be improved by utilizing a placement-new, like ::new ((void*)ptr) T(*i); where ptr is a pointer to a raw, uninitialized memory buffer, like new char[sizeof(T)*size] or returned from get_temporary_buffer.


And if you are wondering why the following works array<char> test = { "abcd" }; with your current implementation then here is an explanation:

  1. array<T> class has a constructor taking const T[], which for T=char is instantiated as array<char>::array(const char*).

  2. List-initialization can be used to call object's constructor.

  3. Your const T[] constructor is not explicit, which means that you can use copy-initialization syntax like below:

    array<char> test = { "test" };
    
  4. The expression sizeof(rhs) / sizeof(T), although not valid as explained above, for T=char evaluates to sizeof(char*) / sizeof(char), which is (most-probably) 4 / 1 = 4.

  5. Your "test" used for initialization has exactly 4 letters, your luck.

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

2 Comments

Thanks for such a thorough answer, Piotr. One question: how does std::initializer_list<T> know the number of arguments passed? Isn't it just a pair of pointers (en.cppreference.com/w/cpp/header/initializer_list)? How are these pointers constructed given something like {1, 2, 3, 4}?
Its a compile time object, Compiler counts them, use .size() member function to know how many are there.

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.