Search code examples
c++vectorvariadic-functions

Vector of tuples of same indexed-elements; determine whether vectors are of same size


I am looking to create a vector that stores a tuple of the same-index elements from different vectors. Although I am able to achieve it, there's a caveat if the caller passes in vectors of different sizes and I am wondering if there's a way to determine whether all passed in vectors are of the same size.

template <typename... Args>
std::vector<std::tuple<Args...>> Foo(const std::vector<Args>&... vecs) 
{
    std::vector<std::tuple<Args...>> result;

    const auto elems = sizeof...(vecs);
    std::cout << "Elems = " << elems << std::endl;

    for (size_t i = 0; i < elems; ++i) 
    {
        result.emplace_back(std::make_tuple(vecs[i]...));
    }

    return result;
}

vector<int> v1 = {1, 4, 8};
vector<double> v2 = {1.1, 4.1, 8.1};
auto vec1 = Foo(v1, v2); // { {1, 1.1}, {4, 4.1}, {8, 8.1} }

Solution

  • The shown code doesn't work as expected anyway. sizeof... gives you the number of arguments in the pack, not the size of the individual elements of the pack.

    Maybe something like this (assuming C++17 or later):

    template <typename Arg1, typename... Args>
    std::vector<std::tuple<Arg1, Args...>> Foo(const std::vector<Arg1>& vec1, const std::vector<Args>&... vecs) 
    {
        if(((vec1.size() != vecs.size()) || ...))
            throw std::invalid_argument("All arguments to Foo must have equal length.");
    
        std::vector<std::tuple<Arg1, Args...>> result;
    
        for (size_t i = 0; i < vec1.size(); ++i) 
        {
            // emplace_back is pointless if you use don't pass
            // the constructor arguments directly
            // Also make_tuple doesn't always produce tuple<Args...>.
            result.emplace_back(vec1[i], vecs[i]...);
        }
    
        return result;
    }
    

    But I would consider whether you really need to limit this function to std::vector specifically. You could accept any ranges and determine the tuple type from std::range_value_t in C++20 or accept iterator pairs and determine the tuple type from dereferencing the iterator.

    You can also consider whether it wouldn't make sense to make the function perfectly-forwarding.

    If you want to allow an empty argument list, then there is ambiguity in how large the resulting vector ought to be. In that case I would suggest adding a specific overload handling it.