c++ how to initialize vector from variadic pack?

138 Views Asked by At

I have a templated Container storing values in vector. There is no problem with strings or ints, but size_t values don't compile. It seems the compiler assumes I want to initialize the vector with ints instead of size_t values. How can I solve this problem?

#include <iostream>
#include <vector>

template <class ValueType>
class Container {
public:
    template <typename... ValueTypes>
    Container(ValueTypes&&... values) :
        currentIndex(0),
        values{ std::forward<ValueTypes>(values)... }
    {}

    const ValueType operator()() const {
        return values[currentIndex];
    }

protected:
    size_t currentIndex;
    std::vector<ValueType> values;
};

int main() {
    Container<std::string> container1("10", "20", "30");    // ok
    Container<int> container2(10, 20, 30);                  // ok
    Container<size_t> container3(10, 20, 30);               // compile error, assumed ints!
}
4

There are 4 best solutions below

3
wohlstad On

10, 20 and 30 are indeed int literals, so the narrowing conversion error/warning is expected.

You can use a u (or ULL) suffix to make them literals compliant with size_t (which is unsigned):

Container<size_t> container3(10u, 20u, 30u);
0
Pepijn Kramer On

These are two methods to choose from (second one is more typesafe but requires a slightly different syntax to use)..

#include <iostream>
#include <vector>
#include <type_traits>

template <class type_t>
class Container {
public:
    

    // Constructor 1
    template <typename... args_t>
    Container(args_t&&... args) :
        m_currentIndex{}
    {
        // pre alocate memory to avoid resizing
        m_values.reserve(sizeof...(args_t));

        // use a fold expression to put all values in the vector
        (m_values.emplace_back(std::forward<args_t>(args)),...);
    }

    // Constructor 2, more typesafe
    template<std::size_t N>
    Container(const type_t (&values)[N]) :
        m_values{std::begin(values),std::end(values)}
    {
    }

    // return a const& here so you avoid a copy
    const type_t& operator()() const 
    {
        return m_values[m_currentIndex];
    }

protected:
    size_t m_currentIndex;
    std::vector<type_t> m_values;
};

int main() 
{
    // Constructor 1
    Container<std::string> container1{"10", "20", "30"};    // ok
    Container<int> container2{10, 20, 30};                  // ok
    Container<size_t> container3{10, 20, 30};               // ok

    // Constructor 2
    Container<std::string> container4{{"10", "20", "30"}};    // ok
    Container<int> container5{{10, 20, 30}};                  // ok
    Container<size_t> container6{{10, 20, 30}};               // ok
}
0
user12002570 On

The problem is that values is not a constant expression(since it is a function parameter) and so the conversion from values to size_t is a narrowing conversion.

This can be seen from dcl.init.list#7 which states:

A narrowing conversion is an implicit conversion from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression whose value after integral promotions will fit into the target type.

(emphasis mine)

Basically, this has the same reason as why std::vector<size_t> v{10, 20, 30} will work but int i = 0; std::vector<size_t> v{i, 20, 30}; will not work. Similarly, const int i = 0; std::vector<size_t> v{i, 20, 30}; will work.

std::vector<size_t> v{10, 20, 30}; //ok because 10, 20 and 30 all are constant expressions

int i = 0; //i is not a constant expression
std::vector<size_t> v{i, 20, 30}; //not ok because i is not a constant  expression and so narrowing conversion

const int  j= 0; //j is a constant expression 
std::vector<size_t> v{j, 20, 30}; //ok because j is a constant expression

One way to solve this is to explicitly write suffix U to each of the literals.

Container<size_t> container3(10U, 20U, 30U);
0
康桓瑋 On

You can forward ValueTypes to construct the actual ValueType, so that the std::vector<ValueType> is constructed via std::initializer_list<ValueType>

template <typename... ValueTypes>
Container(ValueTypes&&... values) :
    currentIndex(0),
    values{ ValueType(std::forward<ValueTypes>(values))... }
{}

Demo