Home > other >  c get std::vector<int> from std::vector<std::tuple<int, float>>
c get std::vector<int> from std::vector<std::tuple<int, float>>

Time:11-03

i want to initialize a const std::vector<int> member variable in the initializer list of a constructor, given a std::vector<std::tuple<int, float>> constructor argument. The vector should contain all the first tuple items.

Is there a one-liner that extracts an std::vector<int> containing all the first tuple entries from an std::vector<std::tuple<int, float>> ?

CodePudding user response:

With C 20 ranges:

struct X
{
    const std::vector<int> v;

    template <std::ranges::range R>
        requires std::convertible_to<std::ranges::range_value_t<R>, int>
    X(R r)
        : v{r.begin(), r.end()}
    {}

    X(const std::vector<std::tuple<int, float>>& vt)
        : X{vt | std::ranges::views::elements<0>}
    {}
};

With ranges-v3:

struct X
{
    const std::vector<int> v;

    X(const std::vector<std::tuple<int, float>>& vt)
        : v{vt | ranges::views::transform([] (auto t) {
                    return std::get<0>(t); })
               | ranges::to<std::vector>()}
    {}
};

And a Frankenstein's monster:

#include <ranges>
#include <range/v3/range/conversion.hpp>

struct X
{
    const std::vector<int> v;

    X(const std::vector<std::tuple<int, float>>& vt)
        : v{vt | std::ranges::views::elements<0>
               | ranges::to<std::vector>()}
    {}
};

CodePudding user response:

Not a one-liner to setup, but certainly one to use - you can write a function to do the conversion, and then the constructor can call that function when initializing the vector member, eg:

std::vector<intconvertVec(const std::vector<std::tuple<int, float>> &arg)
{
    std::vector<int> vec;
    vec.reserve(arg.size());
    std::transform(arg.begin(), arg.end(), std::back_inserter(vec),
        [](const std::tuple<int, float> &t){ return std::get<int>(t); }
    );
    return vec;
}

struct Test
{
    const std::vector<int> vec;

    Test(const std::vector<std::tuple<int, float>> &arg)
        : vec(convertVec(arg))
    {
    } 
};

CodePudding user response:

Adding to @bolov's answer, let's talk about what you might have liked to do, but can't in a one-liner.

  • There's the to<>() function from ranges-v3 from @bolov 's answer - it materializes a range into an actual container (a range is lazily-evaluated, and when you create it you don't actually iterate over the elements). There is no reason it shouldn't be in the standard library, if you ask me - maybe they'll add it 2023?

  • You may be wondering - why do I need that to()? Can't I just initialize a vector by a range? And the answer is - sort of, sometimes, maybe. Consider this program:

    #include <vector>
    #include <tuple>
    #include <ranges>
    
    void foo()
    {
        using pair_type = std::tuple<int, double>;
    
        std::vector<pair_type> tvec {
             {12, 3.4}, {56, 7.8}, { 90, 91.2}
        };
    
        auto tv = std::ranges::transform_view(
            tvec, 
            [](const pair_type& p) { return std::get<0>(p);}
        );
        std::vector<int> vec1 { tv.begin(), tv.end() };
    
        std::vector<std::tuple<int>> vec2 { 
            std::ranges::transform_view{
                tvec,
                [](const pair_type& p) { return std::get<0>(p);} 
            }.begin(),
            std::ranges::transform_view{
                tvec, 
                [](const pair_type& p) { return std::get<0>(p);} 
            }.end()
        };
    }
    

    The vec1 statement will compile just fine, but the vec2 statement will fail (GodBolt.org). This is surprising (to me anyway)!

  • You may also be wondering why you even need to go through ranges at all. Why isn't there a...

    template <typename Container, typename UnaryOperation>
    auto transform(const Container& container, UnaryOperation op);
    

    which constructs the transformed container as its return value? That would allow you to write:

    std::vector<int> vec3 {
         transform(
             tvec, 
             [](const pair_type& p) { return std::get<0>(p); }
         ) 
    } 
    

    ... and Bob's your uncle. Well, we just don't have functions in the C standard library which take containers. It's either iterator pairs, which is the "classic" pre-C 20 standard library, or ranges.

    Now, the way I've declared the transform() function, it is actually tricky/impossible to implement generally, since you don't have a way of converting the type of a container to the same type of container but with a different element. So, instead, let's write something a little easier:

    template <
        typename T,
        template <typename> class Container,
        typename UnaryOperation
    >
    auto transform(const Container<T>& container, UnaryOperation op)
    {
        auto tv = std::ranges::transform_view(container, op);
        using op_result = decltype(op(*container.begin()));
        return Container<op_result> { tv.begin(), tv.end() };
    }
    

    and this works (GodBolt.org).

  • Related