0

I'm trying to write my own Vector2D class and I'm running into an issue when I try to initialize the size of the Vector2D in the constructor.

#include <vector>

template <typename T>
class Vector2D
{
public:
    explicit Vector2D(const size_t& rows, const size_t& columns)
        : m_data(rows, std::vector<T>{ columns })
    {}

private:
    std::vector<std::vector<T>> m_data;
};

int main()
{
    // Compiles fine
    std::vector<std::vector<float>> v1{ 10, std::vector<float>{ 10 }};

    // Results in narrowing conversion warnings!
    Vector2D<float> v2{ 10, 10 };
}

In the first line of main I can easily initialize a 2 dimensional vector and set the size of both dimensions.

In the next line, I try to do exactly the same thing through the constructor of Vector2D but for some reason I get warnings about narrowing conversions!

<source>: In instantiation of 'Vector2D<T>::Vector2D(const size_t&, const size_t&) [with T = float; size_t = long unsigned int]':
<source>:21:32:   required from here
<source>:8:49: warning: narrowing conversion of '(size_t)columns' from 'size_t' {aka 'long unsigned int'} to 'float' [-Wnarrowing]
    8 |         : m_data(rows, std::vector<T>{ columns })
      |                                                 ^
<source>:8:49: warning: narrowing conversion of 'columns' from 'const size_t' {aka 'const long unsigned int'} to 'float' [-Wnarrowing]

Since the Vector2D constructor is essentially doing exactly the same thing as the line that compiles fine why is it resulting in compiler warnings?

tjwrona1992
  • 8,614
  • 8
  • 35
  • 98
  • read the warning carfeully. It complains about `float`, thats the type of elements – 463035818_is_not_an_ai Nov 20 '20 at 20:05
  • @idclev463035818, I know that's the type of the elements, but I don't see anywhere in this code where it is trying to convert something to a `float`. – tjwrona1992 Nov 20 '20 at 20:09
  • there is another constructor that takes a single parameter, the one taking an initializer list. I am looking for a duplicate... – 463035818_is_not_an_ai Nov 20 '20 at 20:09
  • @idclev463035818 I'm not passing an `initializer_list`. I'm passing a `size_t`. (Obviously the compiler may think otherwise, but I can't see how lol.) – tjwrona1992 Nov 20 '20 at 20:10
  • no you are not ;) still looking for dupe – 463035818_is_not_an_ai Nov 20 '20 at 20:12
  • I think I see what you mean. If I change `: m_data(rows, std::vector{ columns })` to `: m_data(rows, std::vector(columns))` it works... But then why does the first line in `main` compile without warning!? Isn't that passing an `initializer_list` as well? – tjwrona1992 Nov 20 '20 at 20:13
  • 2
    That first line in main doesn't do what you think it does. And yes, you are passing an initializer list for your Vector2D declaration. It's the exact reason for your warnings. – sweenish Nov 20 '20 at 20:16
  • 1
    `std::vector{ columns }` calls the constructor that accepts an initializer list, not the one that accepts a size. – n. m. could be an AI Nov 20 '20 at 20:20
  • 1
    Related: [Construct std::vector using initializer list with one element](https://stackoverflow.com/questions/60486512/) which dups [Why is the std::initializer_list constructor preferred when using a braced initializer list?](https://stackoverflow.com/questions/27144054/) – Remy Lebeau Nov 20 '20 at 20:23
  • different quesiton but the answer also helps here https://stackoverflow.com/a/60195947/4117728 – 463035818_is_not_an_ai Nov 20 '20 at 20:25
  • @sweenish, if the first line in main is not doing what I think it does, then why does it not result in a compiler warning as well? – tjwrona1992 Nov 20 '20 at 20:46
  • You honestly think that a lack of warnings is equivalent to correct logic? – sweenish Nov 20 '20 at 22:45
  • @sweenish, no that's not what I'm saying... I'm saying that both use the SAME logic so why are the warnings not the same? Shouldn't the compiler give the same warnings if both statements are really equivalently wrong? – tjwrona1992 Nov 20 '20 at 23:03
  • They're not using the same logic. – sweenish Nov 20 '20 at 23:15

2 Answers2

0

This warning is actually very helpful, if you print the vector produced by this you'll see this

    #include <iostream>
    #include <vector>

    template <typename T> class Vector2D {
    public:
    explicit Vector2D(const size_t &rows, const size_t &columns)
        : m_data(rows, std::vector<T>{columns, 0}) {}
    void print() {
        for (auto vec : m_data) {
            for (auto val : vec)
                std::cout << val << ' ';
            std::cout << ',';
        }
    }

    private:
        std::vector<std::vector<T>> m_data;
    };

    int main() {
        Vector2D<float> v2{10, 10};
        v2.print();
    }

./a.out

10 0
10 0
10 0
10 0
10 0
10 0
10 0
10 0
10 0
10 0

Which is caused by a narrowing conversion from size_t to float with the initializer_list constructor for vector

To fix this you need to replace the braces{} with parentheses() as shown here

#include <iostream>
#include <vector>

template <typename T> class Vector2D {
public:
explicit Vector2D(const size_t &rows, const size_t &columns)
    : m_data(rows, std::vector<T>(columns)) {}

private:
std::vector<std::vector<T>> m_data;
};

int main() {
    Vector2D<float> v2{10, 10};
    v2.print();
}
0

The warning told you everything you needed to know, you were choosing to ignore it. Here's your constructor.

explicit Vector2D(const size_t& rows, const size_t& columns)
        : m_data(rows, std::vector<T>{ columns })
    {}

The initialization section is using the std::initializer_list constructor of std::vector to create a rows x 1 2D vector, where every row vector gets its sole element initialized to columns, which is a std::size_t, which results in a narrowing warning from std::size_t to float. It's using the std::initializer_list constructor because you are using braced initialization. It's as simple as that.

The fix is to simply invoke the correct constructor by changing your braced initialization {} to () in the constructor call in the initialization section.

As to why you're not getting an error when you declared a standard 2D vector in your main(). It's the same reason. Testing it is as simple as printing the dimensions.

#include <vector>
#include <iostream>

int main()
{
    // Compiles fine
    std::vector<std::vector<float>> v1{ 10, std::vector<float>{ 10 }};
    for (auto vec : v1) {
        std::cout << vec.size() << '\n';
}

This prints:

1
1
1
1
1
1
1
1
1
1

You created a 10x1 2D vector because you passed a std::initializer_list. Which is why it's very likely not doing what you think it is. The literal 10 is also an int, and converting it to float won't throw the warning because the two types are likely the same size on your system, and you're explicitly placing 10 in there, which silences narrowing warnings. Testing the exact same logic would have resulted in the exact same error, as seen here:

#include <vector>

int main()
{
    std::size_t val = 10;
    std::vector<std::vector<float>> v1{ 10, std::vector<float>{ val }};
}

If you try to place std::size_t(10) in place of val, it silences the warning, but you've still narrowed your data. The compiler simply assumes you meant to now because it was explicitly done.

Here is your code, fixed up:

#include <vector>
#include <iostream>

template <typename T>
class Vector2D
{
public:
    explicit Vector2D(const size_t& rows, const size_t& columns)
        : m_data(rows, std::vector<T>(columns))  // Only changed {} to ()
    {}

// private:  // For ease of testing
    std::vector<std::vector<T>> m_data;
};

int main()
{
    Vector2D<float> v2{ 10, 10 };
    for (auto vec : v2.m_data) {
        std::cout << vec.size() << '\n';
    }
}

Since your class provides no std::initializer_list constructors, you don't have to worry about it for Vec2D. But std::vector does(#10 in the list), and you kept invoking them, and then insisting that you weren't. Unlike the other answer, I am opting to not provide a default value in your Vec2D constructor, meaning that the Vec2D object will instantiated with values of T(), which will always work if T is DefaultConstructable. The other answer immediately breaks for any class that cannot be initialized with a value of 0, which is not a small number.

In the future, it would be a lot easier for everyone involved if you listened to what you're being told instead of pushing back. You're the one with the broken code and no one on this site owes you anything.

sweenish
  • 4,793
  • 3
  • 12
  • 23
  • 1
    Thank you, the part in the middle explaining why the line in `main` was not causing a warning made it very clear... Although I was not ***insisting*** that I was not invoking an initializer list constructor. I understood that this was probably what was happening, and I was not pushing back. I just did not understand ***why*** it was happening. My comments above made that pretty clear. I've accepted this answer because it is correct and very descriptive. Although you are being quite rude unnecessarily. – tjwrona1992 Nov 22 '20 at 02:28