0

I have an array with items of type variant that I want to iterator over using the generic std::array iterator. Now I want to do the management of the array with my own class array2. However, the variant might also contain an object of type array2 which is instantiated using the variant itself, which means that val is already required as a template parameter.

From a logical standpoint this problem seems solvable: I don't use a data member of type val in array2, this means memory layout of val can be fixed upon definition. However, I would have to forward declare the val-type to use it in the definition of val. Can I do that somehow? Or is there a more idiomatic approach to this problem / a workaround?

I have checked out C++ Forward declare using directive but it doesn't answer my question because it doesn't use recursion (which makes it a lot easier).

CompilerExplorer

#include <variant>
#include <cstddef>
#include <array>


template <typename T, size_t V> struct array2;

template <size_t V>
using val = std::variant<std::monostate, int, array2<val, V>>;

template <typename T, size_t V>
struct array2
{
    using iterator = typename std::array<T, V>::iterator;

    iterator begin_; // array allocation not done in here
    iterator end_;
};

int main() {}

Yields:

<source>:9:54: error: 'val' was not declared in this scope
    9 | using val = std::variant<std::monostate, int, array2<val, V>>;

Update:

Note that array2's size does not depend on the definition of val, except that the using declaration does require it for std::array<val, V> to be complete apparently.

glades
  • 3,778
  • 1
  • 12
  • 34
  • A variant does not use dynamic allocation, the variants are contained in the `std::variant`. Your `val` would need to have enough space to accomodate an array of `val`s where each `val` has room for an array of `val`s etc ad infinitum. – 463035818_is_not_an_ai Jul 28 '22 at 11:18
  • @463035818_is_not_a_number Sorry my array does not store the data directly. I update my example. So there's no dependency of arra2 to val except the using declaration – glades Jul 28 '22 at 11:44
  • Can you tell me what is the size of `val`? `std::variant` size is size of struct holding biggest size type plus enumeration identifying type currently hold by a variant. So what is the size of `val`? You have a recursion with infinitive outcome. – Marek R Jul 28 '22 at 12:49
  • @MarekR Simple, it is sizeof(array) which is 2*sizeof(iterator). The size of the iterator is not something that depends on the size or type of std::array. However: This is the crux, because C++ doesn't know that as it is an implementation detail. – glades Jul 28 '22 at 12:53

2 Answers2

2

No, what you want is not possible.

cppreference says about type-alias using:

The type-id [target type] cannot directly or indirectly refer to identifier [type being defined].

It doesn't matter if the problem is "logically" solvable in some scenarios, as—C++ being statically typed language—all types must be known at compile time, and recursive types simply can't be that because their type resolving process would never end.

Forward declaration is something completely different: with forward declaration you are simply giving type declaration without its definition, but there is no problem with that type itself, and the linker will take care of finding the definition. See this answer for more on forward declarations.

You need to simply get rid of recursion like this, get array2<val, V> out of val. You could use something like this for the same effect:

using val = std::variant<std::monostate, int>;

template <size_t V>
using valOrArray2Val = std::variant<val, array2<val, V>>;
Overjoyed
  • 50
  • 6
0

I've found a solution. In case you typedef a class type you can simply create a proxy type that inherits the class and benefit from the fact that template instantiation is only done when the declaration of proxy is already complete:

LiveDemo

#include <variant>
#include <cstddef>
#include <array>

template <typename T, size_t V>
struct array
{
    using iterator = T*;
};

template <size_t V> struct proxy;

template <size_t V>
using val = std::variant<std::monostate, int, double, bool, array<proxy<V>, V>>;

template <size_t V>
struct proxy : val<V>
{
    using val<V>::variant;

    
};


int main()
{
    proxy<10> doWork = 2.4;
}

Note: This still only works if there are no proxy data members within the proxy class (but this is obvious in this case).

EDIT:

I've changed stuff around and added an example. Now it works. However, keep in mind that proxy is not complete until after the struct closing brace, so I can't define anything including std::array<T, V>::iterator which needs a complete type. I'm now just using T* for an iterator (which i still have to define).

glades
  • 3,778
  • 1
  • 12
  • 34