In C++, how to make a variant that can contain a vector of of same variant?
Asked Answered
E

3

14

I a trying to make a std::variant that can contain a vector of the same variant:

class ScriptParameter;
using ScriptParameter = std::variant<bool, int, double, std::string, std::vector<ScriptParameter> >;

I am getting ScriptParameter redefinition. It think it is possibly because a template parameter cannot be forward declared?

Is there a way to achieve a variant that could also contain an array of same typed variants?

Enwreathe answered 27/11, 2018 at 15:18 Comment(6)
This rather sounds like a more generic flaw in your design. What do you really want to achieve?Wager
I don't think C++ supports the chicken-and-egg pattern.Correggio
Looks like a naming conflict? What are you expecting the compiler to understand ScriptParameter as? The type, or the forward declared class?Cuneate
related/dupe: #39454847Willin
The array specifically shouldn't be possible. What would the size of something like that be?Absolve
@Wager It looks like he's making a Composite pattern node. I actually like the idea.Tevere
J
14

Since the forward declaration says ScriptParameter is a class, you can't use using alias. However, nothing is inherently wrong here, since vector is only a pointer, there is no real circular dependency.

You can use inheritance:

class ScriptParameter;
class ScriptParameter
    : public std::variant<bool, int, double, std::string, std::vector<ScriptParameter> >
{
public:
    using base = std::variant<bool, int, double, std::string, std::vector<ScriptParameter> >;
    using base::base;
    using base::operator=;
};

int main() {    
    ScriptParameter sp{"hello"};
    sp = 1.0;
    std::vector<ScriptParameter> vec;
    sp = vec;    
    std::cout << sp.index() << "\n";  
}
Jargonize answered 27/11, 2018 at 15:25 Comment(2)
Exactly what I did four weeks ago. Sadly making a std::map/std::set of the same is not possible.Plica
Finally, a useful answer. Thanks!Ecology
H
14

Use the type level fixed-point operator.

#include <vector>
#include <variant>
#include <string>

// non-recursive definition 
template<class T>
using Var = std::variant<int, bool, double, std::string, std::vector<T>>;

// tie the knot
template <template<class> class K>
struct Fix : K<Fix<K>>
{
   using K<Fix>::K;
};

using ScriptParameter = Fix<Var>;

// usage example    
int main()
{
    using V = std::vector<ScriptParameter>;
    ScriptParameter k {V{1, false, "abc", V{2, V{"x", "y"}, 3.0}}};
}
Hyrcania answered 27/11, 2018 at 16:46 Comment(2)
You are making me dizzy.Hong
By Zeus, this actually compiles.Aquamarine
H
1

I'm not sure a recursive definition makes sense in this case. It allows arbitrarily many nested vectors inside a single ScriptParameter. (Essentially we're saying that a script parameter is either a single value or an entire forest of values.) Splitting the definition in two might work better:

// Represents the value of a single parameter passed to a script
using ScriptParameter = std::variant<bool, int, double, std::string>;

// Represents a collection of one or many script parameters
using ScriptParameterSet = std::variant<ScriptParameter, std::vector<ScriptParameter>>;

Alternatively, if the goal here is to define a parameter as one of a set of choices plus a vector of those same choices, you could try a little template magic:

template <class T, class U> struct variant_concat;

template <class... T, class U> struct variant_concat<std::variant<T...>, U>
{
  using type = std::variant<T..., U>;
};

template <class T, class U> using variant_concat_t = typename variant_concat<T, U>::type;

using PrimitiveScriptParameter = std::variant<bool, int, double, std::string>;

using ScriptParameter = variant_concat_t<
  PrimitiveScriptParameter,
  std::vector<PrimitiveScriptParameter>>;

This should address Lightness's usability concern below.

Hunch answered 27/11, 2018 at 15:48 Comment(3)
This doesn't really answer the question and I'm not sure we can say that the OP's approach doesn't fit their use case. I am currently working with code that does what they're doing and it makes sense. Your solution simplifies the type definitions a bit but will complicate usage/visitors quite a lot.Plica
It is hard to tell since the OP declined to supply context. Nevertheless, the recursive definition of a parameter does seem a little off to me, so I thought I'd offer this.Hunch
Yep nice one. ^_^Plica

© 2022 - 2025 — McMap. All rights reserved.