6

I need a way to cast between these two types of variables:

std::array< const char*, 3 >* foo;
const char* foo[][3];

Because I need to be able to pass both types to a function. The function can be defined either of these ways, whichever makes conversion easier:

void bar( std::array< const char*, 3 >* param );
void bar( const char* param[][3] );

In this question, Jarod42 suggests using the method here. Is there a cleaner way to do this?

Edit in response to dyp's link

This reinterpret_cast did work for me, but Ivan Shcherbakov describes it as an "ugly dirty hack" I've pasted the code, below... I don't understand why this is a hack, or why something could go wrong with it? Is the template method suggested by Nathan Monteleone necessarily better than this?

void bar( std::array< const char*, 3 >* param ){}

void main( void )
{
    static const char* cStyle[][3] = { NULL };
    std::array< const char*, 3 > foo = { NULL };
    std::array< const char*, 3 >* stlStyle = &foo;

    bar( reinterpret_cast< std::array< const char*, 3 >* >( cStyle ) );
    bar( stlStyle );
}
Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • May we know what are you trying to store in `foo`? – edmz Mar 26 '14 at 19:53
  • I'm pretty sure this is a duplicate.. `std::array`'s size and alignment are not specified AFAIK, therefore a `std::array*` is not compatible to a `X (*arr)[Y]` – dyp Mar 26 '14 at 19:59
  • 2
    Related: [Treat C cstyle array as std::array](http://stackoverflow.com/questions/11205186/treat-c-cstyle-array-as-stdarray) – dyp Mar 26 '14 at 20:02
  • You say that the function can be defined either of those ways, but *must* it be defined in one of those ways? With `template void bar(I start, I end)`, you can pass iterators to either of those containers or indeed any standard container or c-style array or just part of a container. – eerorika Mar 26 '14 at 20:06
  • dyp, I think the point is that the alignment isn't specified, and yes, both versions of foo should be exactly compatible. black, This variable is going to contain 3 dynamically allocated arrays of c-strings. – Jonathan Mee Mar 27 '14 at 12:24
  • Why are you using pointers? – Shoe Mar 28 '14 at 15:50
  • @Jefffrey This is a simplification. Those dimensions are going to be dynamically allocated. – Jonathan Mee Mar 28 '14 at 15:52
  • @JonathanMee, then why aren't you using `std::vector`? – Shoe Mar 28 '14 at 15:52
  • @JonathanMee, what does the algorithm `bar` do? – Shoe Mar 28 '14 at 15:53
  • Also, do you know that `const char* [][3]` is a **bidimensional** array that contains C-strings, while `std::array< const char*, 3 >` is an **unidimensional** array that contains C-strings? – Shoe Mar 28 '14 at 15:54
  • @Jefffrey The `bar` algorithm is a simplification of a mutator and getter function. – Jonathan Mee Mar 28 '14 at 15:55
  • @JonathanMee, Could you please clarify what do you mean exactly by "mutator and getter function"? – Shoe Mar 28 '14 at 15:55
  • @Jefffrey They're both bidimensional, it might be easier to see if I write the C-Style array like this: `const char* ( *foo )[3]` – Jonathan Mee Mar 28 '14 at 16:00
  • @JonathanMee, `std::array< const char*, 3 >*` is a pointer to a **unidimensional** array. Are you saying that you are dynamically allocating an `std::array< const char*, 3 >` with `new[]`? – Shoe Mar 28 '14 at 16:01
  • @Jefffrey A "pointer to a unidimensional array" that's what both `foo`s are. And a dynamically allocated "pointer to a unidimensional array" is by definition a bidimensional array. – Jonathan Mee Mar 28 '14 at 16:04
  • @JonathanMee, What do those three strings in the `std::array< const char*, 3 >` represents in your application? – Shoe Mar 28 '14 at 16:05
  • @Jefffrey If it wasn't clear that I'm doing something sketchy from my variables, it will be after I explain this: The c-strings are code that will be used dynamically. – Jonathan Mee Mar 28 '14 at 17:10
  • @JonathanMee, what I mean is: can't you put those three strings in a `struct`? – Shoe Mar 28 '14 at 18:16
  • Jefffrey, Clearly I _can_. But because I'm working within a preexisting code base, the best course of action would be to modernize my code to the greatest extent possible, while leaving the rest of the code base intact. I'm excited about the solution that Nathan Monteleone led me to, because it accomplishes that, without requiring me to modify code that I don't have the time to digest and then edit. – Jonathan Mee Mar 28 '14 at 18:46

2 Answers2

3

Off the top of my head, the easiest most elegant thing you could do is just make your bar function a template.

template <class T> void Tbar( T param ) {
    char c12 = param[1][2];    // (for example)
}

Of course at that point you lose the ability to enforce that it be of size [3]. So you could do something like hide the template implementation down in a source file, expose both of your original functions as prototypes in a header, and in the cpp file just implement as

void bar( std::array< const char*, 3 >* param ) { Tbar(param); }
void bar( const char* param[][3] ) { Tbar(param); }

The advantage of this sort of approach is that it avoids casting entirely. The disadvantage, I suppose, is that you're a little bit more limited in what you can do inside Tbar, namely you're restricted to whatever operations both char*[][3] and array<char*, 3>* have in common. It would also be harder to store off a pointer to the array if, for example, Tbar was a setter in a non-template class.

Nathan Monteleone
  • 5,430
  • 29
  • 43
  • I think I could do a `static_assert` with `extents` to ensure that the size was 3, right? Then I should be able to proceed with uniform instantiation. – Jonathan Mee Mar 27 '14 at 11:52
  • @JonathanMee You might be able to. I actually have never used `std::extent` before, so would like to know if you have any success with that. – Nathan Monteleone Mar 27 '14 at 13:03
  • Nathan Monteleone Well I had less success than I would have hoped for. I'm having trouble using `std::extent` on both an `std::array` and a C-style array. I'd like to make this work though cause according to Ivan Shcherbakov, I'm a bad person if I don't. To that end I've created a new question: http://stackoverflow.com/questions/22712965/using-stdextent-on-stdarray – Jonathan Mee Mar 28 '14 at 12:35
2

Based on Nathan Monteleone's solution:

template<typename T>
enable_if_t<conditional_t<is_array_v<T>, extent<T>, tuple_size<T>>::value == 3U> bar(T* param){}

I believe this solution is the best of all worlds, cause it avoids the reinterpret_cast that is implementation dependent. And it enforces that param must be of size 3 at compile time.

Note that the conditional_t's value is called only after a type is selected. For more on this see: Short Circuiting Operators in an enable_if

Live Example

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288