问题
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?
回答1:
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";
}
回答2:
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.
回答3:
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}}};
}
来源:https://stackoverflow.com/questions/53502760/in-c-how-to-make-a-variant-that-can-contain-a-vector-of-of-same-variant