5

I am just learning to use std::variant and I would like to declare a type list consisting of (in principle) arbitrarily many of my user defined types. I.e., something like

template<typename T>
struct MyType{
    T x;
};

template<typename T, int N>
MyClass{
public:
    MyType<T> y;
    int z = N;
    double w;
    MyClass(double b){
        w = b;
    }
};

template<typename T>
using my_type_list = std::variant<
    MyClass<T,1>, MyClass<T,289>, MyClass<T,13>, ...., MyClass<T,5001>
>;

template<typename T>
std::vector<my_type_list> my_big_list = {
    MyClass<T,1> { 2.0 },
    MyClass<T,1> { 3.0 },
    MyClass<T,289> { 9.4 },
    MyClass<T, 13> { 1.3 },
    MyClass<T, 5001> {2.5},
    MyClass<T, 5001> {3.2},
    ..... etc....
};

but where the integer N can in principle be anything.

is there any way that this is possible?

user366202
  • 249
  • 2
  • 7
  • 2
    Maybe you're looking for `std::any`? Either it can be anything, or you have to list each and every type. You can't have it both ways. – super Mar 14 '21 at 16:57
  • I'm not I understand where the arbitrarily many types is supposed to come from. Is actually a range [1, N] as you have it here? – Ryan Haining Mar 14 '21 at 16:58
  • When you say N can be anything do you mean it should be configurable from the template parameter? Seeing as this is a template I assume you know at compile time how big N will be? – Borgleader Mar 14 '21 at 16:59
  • @RyanHaining no, not actually a range, which makes it harder. Just a large (maybe a few hundred) number of arbitrary integers. – user366202 Mar 14 '21 at 16:59
  • 1
    Make `N` a second template parameter, of the `my_type_list` template, then write all the voodoo to make it work, like that? – Sam Varshavchik Mar 14 '21 at 17:00
  • @Borgleader yes, at compile time I know the value of all possible Ns. for example, I might have something like "N=17", "N=9232", "N=150"...., "N=5". – user366202 Mar 14 '21 at 17:00
  • C++ compilers are not required to be able to deal with template parameter lists of arbitrary lengths. Iirc 1024 is the recommendation, and the vs compiler can deal with 2048, so no you can only do this with a reasonably large number of elements... – fabian Mar 14 '21 at 17:00
  • 1
    If you don't know all of the types that you want at compile time, then you can use `std::any` like super said. If you want them in a variant then the variant needs to know all of it's possibly-contained types at compile-time – Ryan Haining Mar 14 '21 at 17:01
  • How is your list of possible N values specified in compile time? Why can't you declare the variant as in your code example? – interjay Mar 14 '21 at 17:02
  • @user366202 it seems to me that the `N` here should just be a constructor argument rather than a type argument. – Ryan Haining Mar 14 '21 at 17:02
  • @SamVarshavchik yes, something like that was what I was hoping for, but I don't know how to do this. I guessed such a thing might be possible since one can have variadic templates and therefore functions which take arbitrary type lists, so perhaps something roughly similar is also possible with std::variant? – user366202 Mar 14 '21 at 17:02
  • @interjay I can in principle, it is just that there are a lot of values of N, and I would prefer not to have to re-edit every time the options for 'N' change. – user366202 Mar 14 '21 at 17:03
  • @RyanHaining unfortunately I haven't found a way to make my class work if N is not a template parameter but I will look into this – user366202 Mar 14 '21 at 17:04
  • 1
    But obviously you'd have to edit the value of N somewhere, no? I think this question needs an example more accurately portraying what you're trying to do as the example with the sequential N numbers doesn't seem relevant. – interjay Mar 14 '21 at 17:04
  • Maybe it can't be, but if you can simplify the type and post a separate SO question with that you might get a better result. – Ryan Haining Mar 14 '21 at 17:05
  • If you can calculate the `N`s by some function, you can write a compile-time type traits to exand it into the proper variant. – super Mar 14 '21 at 17:06
  • @super `std::any` should work, but (a) I want to use std::visit if possible, and (b) my understanding is that std::any requires more work to be done at runtime and therefore might be slower (performance is very important) – user366202 Mar 14 '21 at 17:10
  • You've already accepted an answer, but have edited your question after that in a way that invalidates the answers. Please post a new question, if you have a new question. Als9o, what exactly are you trying to do with that `vector`? – cigien Mar 14 '21 at 17:46
  • @cigien yes, the accepted answer doesn't exactly do what I want, but it's close enough that I can adapt my code to it (since there does not seem to be a way to specify exactly what I hoped for). The idea of the `vector` is to store a collection of `MyClass` objects which I can then act on, e.g. use `std::visit` to print some values for each `MyClass`, apply some function to each `MyClass` object returning a numerical value, etc. I.e. something like `for(auto &v : big_list){visit([](auto &&obj){obj.do_stuff();},v);}` – user366202 Mar 14 '21 at 18:29

2 Answers2

1

You indicated that you're looking to use another template parameter, to specify the number of values in the variant. In that case, it's a simple matter to use it to construct a std::integer_sequence, and then use it to create your variant type list.

#include <variant>
#include <type_traits>
#include <utility>

template<typename T>
struct MyType{
    T x;
};

template<typename T, int N>
class MyClass{
    MyType<T> y;
    int z = N;
};

template<typename T, typename sequence> struct my_type_helper;

template<typename T, int ...N>
struct my_type_helper<T, std::integer_sequence<int, N...>> {

    typedef std::variant<MyClass<T, N>...> variant_t;
};

template<typename T, int N>
using my_type=
    typename my_type_helper<T, std::make_integer_sequence<int, N>>::variant_t;

static_assert(std::is_same_v<std::variant<MyClass<int, 0>,
          MyClass<int, 1>,
          MyClass<int, 2>>,

          my_type<int, 3>>);

The static_assert proves that my_type<int, 3> is equivalent to std::variant<MyClass<int, 0>, MyClass<int, 1>, MyClass<int, 2>>.

P.S. The int z = N; should likely be constexpr int z=N;.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
1

You can use std::index_sequence to create a sequence of compile time indices, and then use that to create the std::variant with those indices as a variadic pack of non-type template parameters.

template<typename T, std::size_t ...Is>
auto my_type_list_impl(std::index_sequence<Is...>) 
  -> std::variant<MyClass<T, Is + 1>...>;  // no definition needed, we just need the return type

template<typename T, int N>
using my_type_list 
  = decltype(my_type_list_impl<T>(std::make_index_sequence<N>{}));

Here's a demo

cigien
  • 57,834
  • 11
  • 73
  • 112