2

I have an array of data in a C++/CLI array that I can pass to a native function using pin_ptr<T>, no problem so far. Now, however, I need to pass the array on to a C++/STL function that expects a container such as std::array or std::vector.

The easy way of doing this (which I did first), is to copy element by element.

The second-easiest way is to call std::copy(), see the answer to this question: convert System::array to std::vector.

However, I want to skip the entire copying step and just use the pointer instead. Seeing as std::array requires a template argument to determine its length, I can't create one at runtime (but please do correct me if I'm wrong). Is there a way to create a vector or a different type of STL container, without unnecessary copying of data?

Community
  • 1
  • 1
  • It might work, but assumming that C++/CLI has a [System::Array](https://msdn.microsoft.com/en-us/library/system.array(v=vs.110).aspx?f=255&MSPPError=-2147217396&cs-save-lang=1&cs-lang=cpp#code-snippet-1), it might not be so clean. Also, do you only have basic types? No strings or `ref class` that needs to be transformed? Either of those cases would also preclude passing pointers. – crashmstr Nov 11 '15 at 15:51
  • 1
    The C++20 standard now supports `span` which should allow wrapping a `System::array`. – doug Jun 30 '20 at 18:43
  • @doug That's a great point, worthy of an answer IMHO! – underscore_d Jul 01 '20 at 08:20
  • For pointers that aren't already owned/managed by anyone else, another way to do this would be to use a `unique_ptr` or a `shared_ptr`, which can own/reference dynamic arrays. The fact those come with owning semantics means they won't work for all situations, as does the fact that they don't carry a size with them, but they might work for some situation. They certainly avoid copying, as they just take ownership/reference to an existing pointer. I don't know how often they're practically useful/recommended, but I mention for completion. – underscore_d Jul 01 '20 at 08:24
  • @underscore_d. It's a bit indirect since `std::span` is not available in c++17 and that's the latest version of c++ that supports the c++/cli. But it can be put in a separate file compiled with the latest and ABIs are compatible. Looks like c++17 may be the end of the road for c++/cli. – doug Jul 05 '20 at 04:40

3 Answers3

4

Since C++20 there is a way to use c++ containers with managed arrays in c++/cli that avoids copying the data: std::span

In c++20 it's possible to use std::span to wrap a managed c++/cli array. Then it can be used with standard container algorithms.

Unfortunately, Microsoft doesn't support c++/cli beyond c++17. Consequently one must first pass the pointer and length to a function in a different source file that is compiled using c++latest while compiling the caller's source file using the earlier c++17/cli. Fortunately the ABIs are compatible. This is easily set up for each file in the properties page of Visual Studio 2019.

Here's some sample code that creates a small managed array<double> then calls a function that wraps the managed data with std::span then sorts with std::sort

// file1.cpp compile with /cli
#include <iostream>
using namespace System;
void sortme(double *p, int len);

int main()
{
    array<double>^ v = gcnew array<double> {1.0, 3.0, 2.0, 4.0};
    pin_ptr<double> pin=&v[0];
    int len=v->Length;
    sortme(pin, len);
    for (int i = 0; i < len; i++)
        std::cout << v[i] << "\n";  // prints sorted array
 }

// file2.cpp compile with c++latest
#include <span>
#include <algorithm>
void sortme(double *p, int len)
{
    std::span data_clr(p, len);
    std::sort(data_clr.begin(), data_clr.end());
}
doug
  • 3,840
  • 1
  • 14
  • 18
3

No it's not possible to do without copying, not with the standard containers anyway.

If you're still okay with copying then you should look at the std::vector constructor because I think the easiest way would be to do e.g.

std::vector<T>(your_pointer, your_pointer + number_of_elements)

If you definitely want to avoid copying, then it's not really that hard to write a simple wrapper around the pointer, including simple iterators needed for iteration (the reason it has to be a standard container I guess).


Just for fun and because I had some time over, I created just such a wrapper. It includes indexing and iterators. No bounds-checking.

See https://gist.github.com/pileon/c21cfba496e6c352dd81

Example program using it:

#include <iostream>
#include "pointer_container.h"

int main()
{
    int a[20];
    std::iota(a, a + 20, 0);  // Initialize array

    {
        std::cout << "From array    : ";
        for (const auto item : a)
        {
            std::cout << item << ' ';
        }
        std::cout << '\n';
    }

    pointer_container<int> c(a, 20);
    {
        std::cout << "From container: ";
        for (const auto item : c)
        {
            std::cout << item << ' ';
        }
        std::cout << '\n';
    }
}

Expected output from the program:

From array    : 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
From container: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
-1

Since std::array is just a wrapper you can cast a regular array to a pointer to a std::array. This isn't usable for other containers of course.

#include <array>
#include <iostream>

void test(std::array<int, 10>* pia)
{
    std::cout << (*pia)[0] << std::endl;
}


int main()
{
    int ix[10]{ 0 };
    test((std::array<int, 10> *) ix);
}
doug
  • 3,840
  • 1
  • 14
  • 18
  • 2
    Is this standard compliant? – ofo Aug 09 '19 at 16:36
  • 3
    @ofo No, because no `std::array` exists at the address that got cast, this is undefined behaviour. One should instead use `.data()` to get the pointer to the encapsulated array in a legitimate way, and pass that. – underscore_d Jun 30 '20 at 16:16
  • @underscore_d Agree. It's a hack. While it works on the compilers I use, like other hacks that are UB in the standard best to avoid these things or isolate them in some file reserved for old stuff that's too much work to refactor. As an aside, the new C++20 std:span would solve the OP's problem if they have a compiler that supports it. It would avoid copying and supports most container ops. – doug Jun 30 '20 at 18:40