constructor call is ambiguous...

Discussion in 'C++' started by aaragon, Oct 21, 2007.

  1. aaragon

    aaragon Guest

    Hello! I've been working on enhancing a simple Matrix class that I
    found on Stroustrup's "The C++ Programming Language" and I have a
    problem with ambiguous constructor calls that I don't know if I can
    solve.

    The idea is:

    template <typename T = double>
    class Matrix
    {
    typedef T ValueType;
    valarray<ValueType>* v_; //!< Valarray object used to store the
    matrix elements by rows
    size_t m_; //!< Unsigned integer used to
    store the number of rows
    size_t n_; //!< Unsigned integer used to
    store the number of columns

    public:
    // matrix constructors
    Matrix() : m_(), n_(), v_(NULL) {}
    explicit Matrix(size_t n, ValueType val = 0) : m_(n), n_(n),
    v_(new valarray<ValueType>(val, n*n)) {}
    Matrix(size_t m, size_t n, ValueType val = 0) : m_(m), n_(n),
    v_(new valarray<ValueType>(val,m*n)){}
    Matrix(const Matrix& prototype) {
    // copy number of rows and columns from matrix A
    m_ = prototype.m_;
    n_ = prototype.n_;
    // allocates memory for storing the matrix elements
    v_ = new valarray<ValueType>(*(prototype.v_));
    }
    Matrix& operator=(const Matrix& prototype) {
    // check to avoid self-copy
    if(this != &prototype)
    {
    // deletes dynamically allocated memory
    delete v_;
    // copy number of rows and columns from matrix A
    m_ = prototype.m_;
    n_ = prototype.n_;
    // allocates memory for storing the matrix elements
    v_ = new valarray<ValueType>(*(prototype.v_));
    }
    return *this;
    }
    ....
    ....
    ....

    Now, as you can see the second and third constructor calls are
    ambiguous. Is there a way to get around this? I would like to have
    both constructors. Also, I've been thinking in the best way to have a
    constructor for identity matrices. I thought that makeing a
    constructor:

    Matrix(size_t, string);

    would make a good choice and then comparing the string agains
    "identity" or something like this. But maybe there is a better way to
    do it, probably with an enumerated type?

    Thanks for your suggestions!

     
    aaragon, Oct 21, 2007
    #1
    1. Advertisements

  2. aaragon

    Ron Natalie Guest

    You could always take a pair<int,int> to force the MxN constructor.

    I also suggest style wise that you default the args
    ValueType val = ValueType()
    rather than zero.

    That way you can use it with anything that has default
    initialization semantics (and yes it still works with the
    stupided-assed inconsistant pod types. Default initialization
    for them is still zero initialization even if the compiler
    haphazardsly omits doing it).
     
    Ron Natalie, Oct 21, 2007
    #2
    1. Advertisements

  3. aaragon

    aaragon Guest

    Thanks for the tip on initialization. Regarding the pair<>, I don't
    think that's a good idea because clients probably won't have idea
    about that class. I would like them to write

    Matrix<double> A(4,3);

    and not

    Matrix<double> A(std::make_pair(4,3));
     
    aaragon, Oct 21, 2007
    #3
  4. aaragon

    James Kanze Guest

    Only when T is size_t. Otherwise, you can remove the ambiguity
    by casting to the exact type, e.g.:

    Matrix< double > m1( static_cast< size_t >( 10 ),
    static_cast< size_t >( 20 ) ) ;
    // uses first ctor.
    Matrix< double > m2( static_cast< size_t >( 10 ),
    0.0 ) ; // uses second.

    Not really very user friendly, however (and it still fails for
    Matrix< size_t >). There are several solutions.

    The first thing to remark is that you have violated a basic rule
    of overloading. If you overload for more than one arithmetic
    type, always (without exception) overload for int as well, since
    that's the type of 0, 1, etc. So you should really have four (or
    more) constructors:

    explicit Matrix(size_t n, ValueType val = 0) ;
    explicit Matrix(int n, ValueType val = 0) ;
    Matrix(size_t m, size_t n, ValueType val = 0) ;
    Matrix(int m, int n, ValueType val = 0) ;
    // and possibly...
    Matrix(int m, size_t n, ValueType val = 0) ;
    Matrix(size_t m, int n, ValueType val = 0) ;

    Of course, this will mean that the user will have to cast to an
    exact type if he actually has a long. And it will still be
    ambiguous if T is size_t (whatever that happens to be) or int.
    So this might be a case where the basic rule is wrong.

    Another solution---the one I generally use in such cases---is to
    define a disambiguating type. I have this globally defined,
    since it is useful in many classes:

    enum InitWith { initWith } ;

    You then provide the constructors:

    explicit Matrix( size_t n ) ;
    Matrix( size_t n, InitWith, ValueType val ) ;
    Matrix( size_t m, size_t n ) ;
    Matrix( size_t m, size_t n, InitWith, ValueType val ) ;

    The user then writes:

    Matrix< double > d1( 10, initWith, 3.14159 ) ;
    Matrix< double > d2( 10, 20, initwith, 42 ) ;

    and so on.

    (There are numerous variants on this. You can create a wrapper
    class, so the user writes:
    Matrix< double > d( 10, InitValue( 42) ) ;
    for example. It's a bit trickier to make this work with the
    automatic conversions, however.)

    (FWIW: this technique was widely used before "explicit", to
    ensure that constructors couldn't be used for conversion. For
    example, you're explicit constructor might have a signature:
    Matrix<>::Matrix( Dimension, size_t n ) ;
    where Dimension was "enum Dimension { dim } ;". The user then
    had to write:
    Matrix< double > d( dim, 20 ) ;
    but this was considered better than allowing the implicit
    conversion.)
     
    James Kanze, Oct 22, 2007
    #4
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.