c++constructorinitializer-listsize-tparameterized-constructor

Cpp compiler chooses the wrong constructor


Got a parametrized constructor which accepts size_t variables, and therefore should be called when trying to create an object passing a size_t variable. Instead, compiler tries to call the initializer_list<value_type> constructor, tries to convert the size_t to double, fails and throws an error:

error: non-constant-expression cannot be narrowed from type 'size_t' (aka 'unsigned long') to 'double' in initializer list [-Wc++11-narrowing] Vector whyMe{u};

It does differentiate between String and initializer_list when choosing a constructor, any random integer will still be converted to list of doubles tho, which is also strange as tagging a constructor with explicit (explicit Vector (std::initializer_list<value_type> list)), at least as I understand it, should only allow instances of initializer_list<...> in. Would be grateful for a fix, that doesn't use explicit though, as it may be detrimental to further development.

#include <iostream>

class Vector {
public:
    using value_type = double;
private:
    size_t sz;
    size_t max_sz;
    value_type* values;
public:
    // Constructors
    // parametrized
    Vector (std::initializer_list<value_type> list)
        : sz{list.size()}, max_sz{list.size()}, values{new value_type[list.size()]} {
        
        int tempIterator = 0;
        for (value_type el : list) {
            values[tempIterator] = el;
            ++tempIterator;
        }

        std::cout << "std::initializer_list<value_type> list constructor called" << std::endl;
    }
    Vector(size_t n)
        : sz{0}, max_sz{n}, values{new value_type[n]} {
        
        std::cout << "size_t n constructor called" << std::endl;
    }

    Vector (std::string n): sz{0}, max_sz{0}, values{new value_type[0]} {
        std::cout << "string constructor called " << n << std::endl;
    }

    // Destructors
    ~Vector() {
        delete[] values;
    }
};


int main () {
    std::initializer_list<double> love{1,2,3,4,5,6};
    size_t u{5};
    std::string compiler{"idontwanttolive"};

    Vector wannaDie{love};
    Vector whyMe{u};
    Vector lifeIsNotWorthLiving{compiler};
    return 0;
}

Will also be grateful for any hints to improve my code formatting, use of best practices, etc. cause I am new to the C++ and programming in general.


Solution

  • You need to call Vector whyMe(u); if you want to use a regular constructor and not the initializer-list one. Initializing an object with {} through regular constructors will only come into effect if your class does not provide an initializer-list constructor.

    Take this example with the standard library's vector:

    std::vector<int> v1(10);    // vector of 10 elements with the default value 0
    std::vector<int> v2{10};    // vector of 1 element with the value 10
    

    The reason for the different behavior is that std::vector provides a constructor with initializer list and a constructor that takes size_t.

    For more explanation with examples, see https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-list