3

I have an std::vector containing a variant class. I want to construct a tuple with the same data. Is this possible? The normal construction methods for a tuple seem quite restrictive.

                    //In reality, I'm using JUCE::var.
                    // SimpleVariant is here just to make the example code more explicit.
struct SimpleVariant
{
    SimpleVariant(int i) :                a(i), b("") {}
    SimpleVariant(const std::string& s) : a(0), b(s) {}

    operator int() const { return a; }
    operator std::string() const { return b; }

private:
    int a;
    std::string b;
};


template <typename... T>
struct VariantTuple
{
    VariantTuple(const std::vector<SimpleVariant>& v)
    {
        // how do I initialize the tuple here?
    }

private:
    std::tuple<T...> tuple;
};


        std::vector<SimpleVariant> v{ SimpleVariant(1),
                                      SimpleVariant(2),
                                      SimpleVariant("a") };

        VariantTuple<int, int, std::string> t (v);

Some clarifications based on the comments:

I do not need the tuple to match the array term by term, or to deduce the types from the given array. I want to take a given array and then extract Variants that match a certain type. So, for instance, given the above array v, I would like to be able to construct a VariantTuple<int, std::string>, and have it match the terms "1" and "a". This introduces a host of other problems beyond the scope of my original question. But the question I'm interested in right now is whether it is even possible to construct a tuple based on an array in the first place.

Liam Goodacre
  • 381
  • 1
  • 10
  • The question title mentions arrays, but the question test and example use `vector`s. Which are you asking about? – François Andrieux Mar 20 '20 at 15:54
  • This would be possible if the resulting tuple is given as a function parameter rather than a straight return value, but it would require compiling every possible permutation of types, which quickly blows up in variant size and vector length. – chris Mar 20 '20 at 15:56
  • @chris You would need to set an upper bound on the vector size to compile every possible tuple that could be generated. It's not very practical. You could do this with `std::array` though. – François Andrieux Mar 20 '20 at 15:57
  • @FrançoisAndrieux, Right, it's doable for low lengths and the compile times are really bad for higher anyway. – chris Mar 20 '20 at 16:00
  • Are you actually using `SimpleVariant` or is it just an example? Could you use `std::variant` instead? `SimpleVariant ` looks extremely hard to use as a `variant` and would make anything you are trying to do with it much more complicated. – François Andrieux Mar 20 '20 at 16:10
  • There might be cases where you know both of those numbers is below some threshold without actually knowing what the specific numbers are. Granted a fixed-capacity vector would better represent that for the vector side of things. – chris Mar 20 '20 at 16:14
  • As I mentioned in my first comment, I am actually using JUCE::var. I just wrote SimpleVariant in order to simplify the example, although I guess it is actually causing more confusion. As for the array, it's JUCE::Array, which is basically equivalent to std::vector. – Liam Goodacre Mar 20 '20 at 16:18
  • @FrançoisAndrieux, I already know what the tuple will be. I have edited the original post--hopefully this clarifies. – Liam Goodacre Mar 20 '20 at 16:52
  • @LiamGoodacre That clears it up, thank you. It should be possible. But you mention arrays twice while the code uses `vector`. Using arrays and `std::vector` will produce very different answers. In addition, please clarify if you *need* to use `SimpleVariant` or if `std::variant` is usable. `SimpleVariant` is so incomplete that it may be impossible to do what you want with it. – François Andrieux Mar 20 '20 at 16:58
  • @FrançoisAndrieux, I'm using JUCE::Array, which is a dynamic array basically equivalent to std::vector. It's passed in as an argument, so I have no option of using anything else. The variant that I'm using is JUCE::var (again, non-negotiable). SimpleVariant is just dummy code that I wrote for the example, because I thought it would be bad practice to bring 3rd party code into the question. I'm afraid that I'm not that familiar enough with std::variant, but I don't think that it would fit my context. – Liam Goodacre Mar 20 '20 at 17:06
  • @LiamGoodacre Ok, that seems clear to me. I would recommend changing "array" to "vector". It's presumed in C++ that arrays refer to fixed length containers and vectors refer to dynamic length containers. – François Andrieux Mar 20 '20 at 17:09

1 Answers1

1

Well I'm not sure if you're asking to dynamically deduce the number of vector elements and construct the tuple, which isn't possible, but here you go. I've used std::index_sequence to deduce the number of tuple elements depending of VariantTuple's argument size. This requires C++17 as it uses fold-expression.

#include <initializer_list>
#include <string>
#include <vector>
#include <tuple>
#include <utility>
#include <type_traits>
#include <ostream>
#include <iostream>

struct SimpleVariant
{
    SimpleVariant(int i) :                a(i), b("") {}
    SimpleVariant(const std::string& s) : a(0), b(s) {}

    operator int() const {
        return a;
    }

    operator std::string() const {
        return b;
    }

    int a;
    std::string b;
};

template<typename V, size_t... dim, typename... Args>
auto populate_tuple(const V& vec, std::index_sequence<dim...>, const std::tuple<Args...>& t) {
    return std::make_tuple(static_cast<std::remove_reference_t<decltype(std::get<dim>(t))>>(vec.at(dim))...);
}

template<size_t... dim, typename... Args>
std::ostream& dump_tuple(std::ostream& out, const std::tuple<Args...>& tpl, std::index_sequence<dim...>) {
    ((out << std::get<dim>(tpl) << ","), ...);
    return out;
}

template<typename... T>
struct VariantTuple
{
    VariantTuple(const std::vector<SimpleVariant>& v) : tpl(populate_tuple(v, std::make_index_sequence<sizeof...(T)>{}, tpl)) {}

    template<typename... V>
    friend std::ostream& operator <<(std::ostream& out, const VariantTuple<V...>& vt) {
        return dump_tuple(out, vt.tpl, std::make_index_sequence<sizeof...(V)>{});
    }
private:
    std::tuple<T...> tpl;
};

int main() {
    std::vector<SimpleVariant> v { 
        SimpleVariant(1),
        SimpleVariant(2),
        SimpleVariant("a") 
    };
    VariantTuple<int, int, std::string> t (v);
    std::cout << t << std::endl;

    return 0;
}
Samik
  • 575
  • 2
  • 8
  • 19
  • Well you've already constructed the vector, haven't you? Executing URL, https://wandbox.org/permlink/QL6DP5EC1tBrFZHB – Samik Mar 20 '20 at 17:04
  • I've used user-defined conversion operator for that – Samik Mar 20 '20 at 17:04
  • Changing `t` to `std::tuple & t` in `populate_tuple` fixed it for me. It was performing a copy of an uninitialized `tuple`. – François Andrieux Mar 20 '20 at 17:08
  • @FrançoisAndrieux, you need to pass the tuple by reference, I'll edit the answer, gcc didn't show the warning, clang did. – Samik Mar 20 '20 at 17:10
  • This seems fine, with the change to `t`. But passing the uninitialized `tpl` as an argument to `populate_tuple` is code smell, it should raise concerns in any code review. Perhaps you could pass the tuple by passing `decltype(tpl)` as a template argument instead, and use `std::tuple_element_t` instead of `decltype(std::get(t))>`. Edit : Example : https://godbolt.org/z/9tCvc7 – François Andrieux Mar 20 '20 at 17:13
  • @FrançoisAndrieux What if I pass a new tuple based on `decltype(tpl)`, e.g. decltype(tpl){}, AFAIK anything inside decltype() isn't executed. – Samik Mar 20 '20 at 17:20
  • Yes, it isn't an error. I did say it was fine. But it is suspicious to pass `tpl` anyway. It would be an improvement to show at the call site that the tuple isn't actually accessed by passing only the type. – François Andrieux Mar 20 '20 at 17:42