1

I'm writing array wrapper in C++ (like std::array). The motivation was to make array-wrapper a derivative of interface. This enables to pass arrays to functions without making them templated (through interface). The way of implementing it is a little suspicious-looking so I want to ask if the code below is legal in C++ or not? The example shows the way of using union members, I tried to make the smallest example so Interface and other stuff is not present here. Main problem is union member usage in that way. The reason I do so it to make possible to create array of types with no default c-tor (Unfortunately, this example doesn't show this too).

template<typename T, size_t N>
class Array
{
    public:

    Array() : 
        m_data( m_originalObjects ),
        m_len(N),
        m_place()
    {        
        for( decltype(N) i = 0; i < N; i++ )
        {
            new( &m_data[i] )T();
        }
    }
        //c-tor to objects without default c-tor
        template<typename ... TCon>
        Array( TCon && ... values ) :
            m_data( m_originalObjects ),
            m_len(N),
            m_originalObjects{ static_cast<T>(values)... }
        {
        }

    private:

    T * m_data = nullptr;
    size_t m_len = 0;

    union
    {
        char m_place[sizeof(T) * N];
        T m_originalObjects[N];
    };
};

curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • _"...array-wrapper a derivative of interface...."_ I don't see how you have achieved this. Every instantiation of the `Array` template will be a different class. Please show an example of the use of the template. – Richard Critten Oct 30 '19 at 10:29
  • The problem here is accessing inactive union members. What's wrong with just having a plain `T[N]` member? Also, adding type erasure by rewriting all the basic library classes is possibly the wrong way to go. You can easily write a type erasure construct that wraps arbitrary template classes... – Max Langhof Oct 30 '19 at 10:29
  • @RichardCritten It appears that the common (non-templated) base class is missing (because it's not really what the question is about). – Max Langhof Oct 30 '19 at 10:29
  • @MaxLanghof - didn't want to guess would rather ask for clarification. – Richard Critten Oct 30 '19 at 10:30
  • 2
    Possible duplicate of [Is pointer arithmetic on inactive member of a union UB?](https://stackoverflow.com/questions/48188737/is-pointer-arithmetic-on-inactive-member-of-a-union-ub) – Language Lawyer Oct 30 '19 at 10:44
  • 1
    Might be also relevant: [treating memory returned by operator new(sizeof(T) * N) as an array](https://stackoverflow.com/q/53451770/580083). There are even discussions whether `std::vector` is implementable without UB, such as [here](https://stackoverflow.com/q/52996590/580083) or [here](https://stackoverflow.com/q/40165022/580083). – Daniel Langr Oct 30 '19 at 10:51
  • @MaxLanghof - I dont use plain T[N] because I want be able to create arrays of elements without default c-tor. As a result I make something like what you recommended (type-erasure construct that wraps c-arrays). Sorry for incomplete information, I edit answer a bit – ivan bubnikov Oct 30 '19 at 11:15
  • 1
    @RichardCritten It looks to me like this code has been simplified in order to ask the question without needing to talk about all the other parts of the code. – user253751 Oct 30 '19 at 11:18
  • "*I dont use plain T[N] because I want be able to create arrays of elements without default c-tor*" But you are calling `T()` in the ctor of `Array`. So contrary to what you want to achieve, `Array`'s ctor won't compile if `T` doesn't have a default ctor. – sebrockm Oct 30 '19 at 11:27
  • Language Lawyer, thanks. Doing so is UB as I expect. Is there any way to activate proper member in union so that there will be no UB anymore? – ivan bubnikov Oct 30 '19 at 11:27
  • @sebrockm add c-tor to this case in example code – ivan bubnikov Oct 30 '19 at 11:31
  • 2
    Ok, but now, again, I don't see why you need the union at all and why a plain `T[N]` wouldn't suffice... if you have two ctors, one using `T()` and one using `T(TCon)` for initializing the elements of `m_originalObjects`, then at which place the union member `m_place` is needed at all? – sebrockm Oct 30 '19 at 11:42
  • @sebrockm, thanks, using simple T[N] work as I need to without using union and other dirty things – ivan bubnikov Oct 30 '19 at 11:56
  • Answering the question in the title: not only using placement-new separately for each array element is correct, it's better than using placement-new for the entire array at once (because latter is allowed to insert arbitrary metadata at the beginning of the array, which is nonsense but notably is done by MSVC). – HolyBlackCat Oct 30 '19 at 19:28
  • @sebrockm I don't think illustrating code has to show the usefulness of a construct in Q about whether a construct is well defined. Using raw storage gives obvious flexibility advantages, not limited to using a type that doesn't have a default ctor. – curiousguy Oct 30 '19 at 19:29
  • @curiousguy absolutely agreed. That's why I just mentioned it as a comment and not as an answer. – sebrockm Oct 30 '19 at 19:35
  • @MaxLanghof For a few revision of the C++ std, neither active union member nor accessing a union member was defined. It's still a mess. What is `U.x` if `U` is a union instance and `x` not the active member? What is a lvalue? What is an object? C++ is broken. – curiousguy Oct 30 '19 at 19:58

1 Answers1

0

Problem solved, I expect that having c-style array of types without default c-tors as class member will fail compilation so I used union to "fix" this problem. The truth is that there is no such problem, thanks @sebrockm for opening my eyes on it. The following code is what I really needed


template<typename T, size_t N>
class Array
{
    public:

    Array() : 
        m_data( m_originalObjects ),
        m_len(N),
        m_originalObjects{}
    {        
    }

    //конструктор с параметрами
    template<typename ... TCon>
    Array( TCon && ... values ) :
        m_data( m_originalObjects ),
        m_len(N),
        m_originalObjects{ static_cast<T>(values)... }
    {
    }

    private:

    T * m_data = nullptr;
    size_t m_len = 0;

    T m_originalObjects[N];
};
  • 1
    What is the *point* of the `m_data` member? Have you considered how it behaves in the implicit copy constructor and assignment? – eerorika Oct 30 '19 at 12:20
  • @eerorika, Actually m_data and m_len are members of base class, which is templated only by T. They are present in example to show the usage of union (in question example). – ivan bubnikov Oct 30 '19 at 12:36
  • @ivanbubnikov This looks like all you want is something in between `std::array` and `std::vector`, i.e. something that implements run-time sizes but cannot be resized. A wrapper around `std::vector` would do that pretty easily. Or do you actually need the compile-time size for a different purpose? – Max Langhof Oct 30 '19 at 12:55
  • @Max Langhof, I want to have something like StringView(base class to template array) to use fixed-size array to pass it to functions without need to make them template. – ivan bubnikov Oct 30 '19 at 13:02
  • This is a typical XY problem. You should have been asking something along the lines of "I want to have something like StringView(base class to template array) to use fixed-size array to pass it to functions without need to make them template" in the first place. – L. F. Oct 31 '19 at 11:48
  • @L.F. sorry, my fault. When I asked about union I have no doubt that my way to implement StringView-like class was right. The only annoying thing was the union usage in such strange manner – ivan bubnikov Nov 01 '19 at 08:05