1

I am almost sure that this cannot be done, but I will ask anyway. I have to use a C based library, which defines a numeric vector as array of floats, and lots of arithmetic functions to use them. I want to create a trivial class that can be easily casted to that type, with the addition of useful operators. Let's see a MWE:

#include <iostream>

using vector_type = float[3];

class NewType
{
public:
    float& operator [](std::size_t i) { return v[i]; }
    const float& operator [](std::size_t i) const { return v[i]; }

    operator vector_type& () { return v; }
    vector_type* operator & () { return &v; }

private:
    vector_type v;
};

int main()
{
    NewType t;
    t[0] = 0.f; t[1] = 1.f; t[2] = 2.f;

    const vector_type& v = t;
    std::cout << "v(" << v[0] << "," << v[1] << "," << v[2] << ")" << std::endl;

    return 0;
}

This works flawlessly. The problem arises when we start using arrays. Let's write a new main function:

int main()
{
    constexpr std::size_t size = 10;
    vector_type v1[size];                   // OK
    NewType v2[size];                       // OK
    vector_type* v3 = v2;                   // No way, NewType* cannot be
                                            // converted to float (*)[3]
    vector_type* v4 =
        reinterpret_cast<vector_type*>(v2); // OK

    return 0;
}

The reinterpret_cast works, but it makes the code less readable and the conversion between vector_type and NewType not transparent. As far as I know, it is not possible, according to C++11 and C++14 standards, to make the NewType class implicitly castable when using arrays. Is it completely true? Are there any sort of caveats that allow this convertion?

P.s.: Please, do not start commenting about the risks of using reinterpret_cast and so on. I am aware of the risks, I know that the compiler could add some padding, and I already have some static_assert checks to avoid memory problems.

[Edit] I want to make the problem easier to understand. Let's make a different example:

struct original_vector
{
    float x;
    float y;
    float z;
};

class NewType : public original_vector
{
public:
    /* Useful functions here */
};

If this would be my case, everything would be easy! The type used in the C library would be original_vector, and I could create a derived class and I could add any sort of method.

The problem is that, in my real case, the original_vector is not a class/struct, but a raw array! And obviously, I cannot inherit it. Maybe now it is more clear the reason I am asking this question. ;)

dodomorandi
  • 1,143
  • 1
  • 11
  • 18
  • 2
    Compilers can and will take all sorts of advantage of undefined behaviour that goes beyond padding. UB is UB. – Kerrek SB Sep 17 '15 at 16:04
  • 1
    I see no 'useful operations' you added in `NewType`. In this case, details matter. Is the actual `NewType` standard layout, and if not, why not? ... having both implicit conversion to `vector_type&` and `vector_type*` seems like a bad idea. – Yakk - Adam Nevraumont Sep 17 '15 at 16:23
  • @Yakk I wrote, as explained before, a minimal working example. It is completely useless for the purpose of my question the implementation of every possible function related to linear algebra. Lots of libraries could be useful for my purpose (i.e.: eigen3, armadillo...), but I need a specific case and I have to write my implementation. However, it is not strictly related to this question. – dodomorandi Sep 17 '15 at 18:50
  • An alternative solution would be to wrap the C library – M.M Sep 17 '15 at 21:30
  • 1
    It's not just alignment problems that can be caused by `reinterpret_cast`. Undefined Behaviour can [easily have unexpected results](http://stackoverflow.com/q/32506643/), and this cast is causing UB by violating the strict aliasing rule. – dyp Sep 18 '15 at 00:20
  • There are several workarounds for wrapping arrays in custom types. One is to write a class template which contains the array as a data member, but this cannot be used with runtime bounds. OTOH, for arrays with runtime bounds, using a wrapper of `std::vector` is probably more appropriate. Both can be combined into some template parametrized by the storage (array / vector). -- Unrelated alternative: Use free functions, maybe with a lightweight non-owning pointer + size (`array_view`-like) wrapper for type safety. – dyp Sep 18 '15 at 00:24

2 Answers2

0

I think that it is not the best solution, but it is the best I can think using C++14 capabilities. Maybe, if runtime-sized member allocation is introduced in future standards (the proposal for C++14 has been rejected), a better solution will be possible. But for now...

#include <iostream>
#include <memory>
#include <cassert>

using vector_type = float[3];

class NewType
{
public:
    float& operator [](std::size_t i) { return v[i]; }
    const float& operator [](std::size_t i) const { return v[i]; }

    operator vector_type&  () { return v; }
    vector_type* operator & () { return &v; }

private:
    vector_type v;
};

class NewTypeArray
{
public:
    NewTypeArray() : size(0), newType(nullptr) {}
    NewTypeArray(std::size_t size) : size(size) { assert(size > 0); newType = new NewType[size]; }
    ~NewTypeArray() { if(size > 0) delete[] newType; }

    NewType& operator[](std::size_t i) { return newType[i]; } 
    operator vector_type* () { return static_cast<vector_type*>(&newType[0]); }

private:
    std::size_t size;
    NewType* newType;
};

static_assert(sizeof(NewType) == sizeof(vector_type) and sizeof(NewType[7]) == sizeof(vector_type[7]),
    "NewType and vector_type have different memory layouts");

Obviously, the NewTypeArray could be modified, implementing vector-oriented methods, move constructor and assignment (like in my real-case code). Instances of NewTypeArray could be directly passed to functions which takes vector_type* as argument and, thanks to the static_assert, there should not be any sort of problems with memory management.

dodomorandi
  • 1,143
  • 1
  • 11
  • 18
-1
#include <iostream>
#include <vector>

typedef std::vector<float> vector_type;

class NewType: public vector_type
{
public:
   void MyMethod() {std::cout << "extending the vector type!" << std::endl;}

};

int main()
{
   NewType myNewTypeVector[10];
   myNewTypeVector[0] = NewType();
   myNewTypeVector[0].push_back(1);
   myNewTypeVector[0].push_back(2);

   vector_type* p = myNewTypeVector;

   std::cout << "Content of my_vector index 0: " << p[0][0] << std::endl;
   std::cout << "Content of my_vector index 1: " << p[0][1] << std::endl;
   std::cout << "Content of myNewTypeVector index 1: " << myNewTypeVector[0][1] << std::endl;

   return 0;
}
rkachach
  • 16,517
  • 6
  • 42
  • 66
  • I know that they are incompatible types, obviously. I need to work with the original data, not with copies (otherwise these problems would not exist at all ;) ), therefore the code from your answer is not what I expect. What I want to do is *reference* `NewType[]` as `vector_type[]`. I edited the class to allow one easy thing: now it is possible to write down `NewType t1; vector_type* t2 = t1;`. I would like to allow the same thing when NewType is inside an array, without the use of `reinterpret_cast`. – dodomorandi Sep 17 '15 at 19:16
  • However, as I said initially, AFAIK is not possible, I just want to be sure. – dodomorandi Sep 17 '15 at 19:23
  • AFAIK it's not possible to interchange references between two arrays which base type is not the same. The other possible solution (though not recommended http://stackoverflow.com/questions/16188093/inheriting-from-stdvector) is to use a std::vector for your vector_type. This way you can pass it to C APIs and at the same time you can inherit and add functionality (see my edited answer). Hope this helps but I'm still not sure if this fullfill your needs. – rkachach Sep 17 '15 at 21:48
  • Unfortunately your idea cannot be applied, because I cannot change `vector_type`, which is declared inside the other library. I edited my question, adding a more clear example of what I need. However, thank you: it is a pleasure when people like you really try to understand your problem and try to help you! – dodomorandi Sep 17 '15 at 22:24