Hi guys,

I'm sure that the answer to this is simple, but I don't know what it is and it's bugging me.

For reasons that aren't important, I'm trying to make a kind of light-ish weight array class. I want to be able to use syntax like that of std::vector for the at() method:

std::vector v(10,1);

std::cout << v.at(3) << std::endl;   /* uses const_reference at(size_type __n) const */
v.at(3) = 3;                         /* uses reference at(size_type __n)             */

So, I've tried to implement my own version of this in a simple Array class:

#include <stdexcept>
#include <iostream>

template< typename __T >
class Array{
public :
        typedef const __T& const_reference;
        typedef __T& reference;

        /* some constructors */
        Array(){
            is_initialised__ = false;
            shallow_copy__ = false;
            Sz__ = 0;
        }
        Array(unsigned new_size){
            try{    Data__ = new __T[new_size];    }
            catch(std::exception &e){    throw std::exception(e);         }

            is_initialised__ = true;
            shallow_copy__ = false;
            Sz__ = new_size;
        }
        Array(const Array< __T > &original){
            Sz__ = original.size();
            is_initialised__ = !original.is_null();
            Data__ = original.Data__;
            shallow_copy__ = true;
        }

        /* Destructor */
        ~Array(){
            if(is_initialised__ && (shallow_copy__ == false))    delete[] Data__;
        }

        /* Unsafe access operator */
        reference operator[](const unsigned n_){
            if(shallow_copy__) /* Make a deep copy */
                pv_deep_copy__();

            return *(this->Data__ + n_);
        }
        const_reference operator[](const unsigned n_) const{
            return *(this->Data__ + n_);
        }

        /* Safe access operator */
        reference at(const unsigned n_){
            std::cout << "reference " << n_ << ": ";  // some debugging text
            if(i < Sz__ && is_initialised__){
                if(shallow_copy__)     /* Make a deep copy */
                    pv_deep_copy__();

                return (*this)[n_];
            }
            else            throw std::exception();
        }
        const_reference at(const unsigned n_) const{
            std::cout << "const_reference " << n_ << ": ";    // some debugging text
            if(i < Sz__ && is_initialised__)    return (*this)[n_];
            else            throw std::exception();
        }

        /* Clear the array */
        void clear(){
            if(is_initialised__){
                if(shallow_copy__)   Data__ = NULL;
                else                 delete[] Data__;

                is_initialised__ = false;
                Sz__ = 0;
            }
        }

        /* Check some status-type things */
        unsigned size() const{    return Sz__; }
        bool is_null() const{     return !is_initialised__;   }
private :
        __T *Data__;
        unsigned Sz__;
        bool is_initialised__;
        bool shallow_copy__;

        /* Perform a deep-copy of the data */
        void pv_deep_copy__(){
            std::cerr << "(Making deep copy) ";    // Some debugging text
            __T * const Dt__ = Data__;
            try{    Data__ = new __T[Sz__];    }
            catch(std::exception &e){   throw std::exception(); }
            for(unsigned i = 0; i < Sz__; ++i)
                *(Data__ + i) = *(Dt__ + i);
            shallow_copy__ = false;
        }
};

So, if I then write something that uses this class:

#include "test_arraytemplate.h"

int main(){
    /* Make an Array */
    Array< int > a(10, 2);
    
    /* Make another array using the copy ctor */
    Array< int > b(a);

    /* Should use reference at() */
    a.at(1) = 3;

    /* Should use reference at() for 'a' and const_reference at() const for 'b' */
    a.at(1) = b.at(1);

    return 0;
}

However, const_reference at() const never gets called. Can anyone see why this is? As far as I can see, my version and the std::vector version have pretty much identical signatures, so why does it work from the STL, but not for me? I need to be able to make the distinction because I want to try and use a shallow copy type system, so when the copy c'tor is invoked it doesn't copy the actual data, and it won't as long as const methods are used. When a method tries to change the data a deep copy is taken. However, this problem is screwing that up :o(

However, const_reference at() const never gets called. Can anyone see why this is?

None of your objects are const. The compiler isn't exactly going to say "oh, that call is on the right hand side of an assignment, the programmer must want me to use the const version of this member function even though the non-const version is a perfect match".

You need to come up with another way of differentiating between the overloads than magic. Maybe perform your special logic for the at member functions and just do regular indexing for the subscript operators? Or perhaps an extra parameter that defaults to your most common behavior:

reference at(const unsigned n_, copy_semantics = DEEP);
const_reference at(const unsigned n_, copy_semantics = DEEP);
a.at(1) = 3;
a.at(1) = b.at(1, SHALLOW);

None of your objects are const. The compiler isn't exactly going to say "oh, that call is on the right hand side of an assignment, the programmer must want me to use the const version of this member function even though the non-const version is a perfect match".

I guess. I thought that the compiler might do something similar to a principle of least privilege, but it's probably one of those things that ends up being too restrictive if you think about it a bit.

You need to come up with another way of differentiating between the overloads than magic. Maybe perform your special logic for the at member functions and just do regular indexing for the subscript operators? Or perhaps an extra parameter that defaults to your most common behavior:

But what about std::vector ? It does have both the functions that I specified above (at least in gcc 4.4.5). Is it the case that only one of them is ever actually used by the compiler, or is there something clever that I'm missing?

But what about std::vector ?

What about it? If you used vector instead of Array in your example, the non-const at would still be chosen. You ask the question as if vector does something different.

Is it the case that only one of them is ever actually used by the compiler

The const/non-const overloads are intended strictly to handle const/non-const objects. The constness of your object determines which is called.

The const/non-const overloads are intended strictly to handle const/non-const objects. The constness of your object determines which is called.

Ah, I see. So, if one were to redefine the assignment operator for, say, integers as int &operator=(const int &right) , then the const_reference version would be called? (this isn't something I'm suggesting that I might try to do, just an example)

Ah, I see. So, if one were to redefine the assignment operator for, say, integers as int &operator=(const int &right) , then the const_reference version would be called? (this isn't something I'm suggesting that I might try to do, just an example)

Well, you can't do that because overloading operators on built-in types isn't allowed. But I get what you mean. The const/non-const selection really only matters if you have two overloads that only differ in the constness of the member function[1], ie:

int& operator[](int i)       { return _base[i]; }
const int& operator[](int i) const { return _base[i]; }

In the presence of both, the constness of this will determine which operator is used. If this is const, the const overload will be used. If this is not const, the non-const overload will be used. If you just have a member function that's const, it's slightly different:

// No non-const overload
const int& operator[](int i) const { return _base[i]; }

In this case, there's no choice to be made, so whether this is const or not, the const member function will always be selected because a const member function can be called on a non-const object. A const member function simply says that the member function won't change the state of the object.


[1] Note that return types aren't considered in overloading, so the difference between int& and const int& is irrelevant to overload resolution.

That's all very helpful (we'll to my understanding, but not the easy resolution of my programming issue :) ).

I think I'll just go with making two distinct functions, like

const_reference get(const unsigned n_) const;
reference at(const unsigned n_);

That should enable me to do what I want for now :)

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.