4

Let's say that I have a vector<string> input and I need to pass this to a function that takes a const char** argument. My thought had been to use a unique_ptr like this:

const auto output = make_unique<char*>(size(input));

But I can't seem to turn a const unique_ptr<const*> into a const char**. Is there a way to accomplish this, or perhaps a simpler alternative?

Ron
  • 14,674
  • 4
  • 34
  • 47
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • @Ron Well... `unique_ptr` is the name of a standard object, so it seemed right to preserve it's capitalization... – Jonathan Mee Jul 16 '18 at 15:33
  • 4
    What is `const*` supposed to be? – Ron Jul 16 '18 at 15:35
  • 1
    Does the function take a `char**` so that it can change what the `char*` points to or is it expecting an array of strings? – Miles Budnek Jul 16 '18 at 15:35
  • @MilesBudnek The function is in a C-API. It's doing a deep copy from all the strings. So we're really talking about a `const char* const*` but for purposes of the question we should probably treat it like I didn't know that and it was actually asking for a `const char*`. – Jonathan Mee Jul 16 '18 at 15:37
  • Am I missing something? doesn't the `get` member function do essentially what you want? – Tim Seguine Jul 16 '18 at 15:45
  • @TimSeguine Sadly it does not... Unless we change to `const auto output = std::make_unique(size(v));` as suggested by the answers. I also thought it did, hence the question. – Jonathan Mee Jul 16 '18 at 15:55
  • 1
    @JonathanMee I was so convinced you actually wrote the array overload that my brain completely shut off. – Tim Seguine Jul 16 '18 at 15:57
  • @TimSeguine Well apparently mine too when I wrote it :( – Jonathan Mee Jul 16 '18 at 15:59
  • Possible duplicate of https://stackoverflow.com/questions/26032039/convert-vectorstring-into-char-c – Galik Jul 16 '18 at 16:01

3 Answers3

4

I would just build a vector of pointers to the c_str()'s of the strings and then get a pointer to that.

std:vector<const char*> pointers;
pointers.reserve(input.size());
for (const auto& e : input)
    pointers.push_back(e.c_str()); // get const char *'s
auto argument = pointers.data(); // get pointer to const char*'s - const char**

Or using a unique_ptr

auto pointers = std::make_unique<const char*[]>(size(input))
for (size_t i = 0; i < input.size(); ++i)
    pointers[i]= input[i].c_str(); // get const char *'s
auto argument = pointers.get(); // get pointer to const char*'s - const char**
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 1
    @JonathanMee You could do `auto pointers = std::unique_ptr(new const char*[input.size()]);` and then do the for loop. – NathanOliver Jul 16 '18 at 15:40
  • 1
    @JonathanMee It's more that there's no good reason to bother with `std::unique_ptr` in this situation, since `std::string` is the correct ideomatic expression for storing owned Strings, and `std::unique_ptr` just adds additional code cruft. – Xirema Jul 16 '18 at 15:41
  • 1
    @NathanOliver Right you are `const auto output = make_unique(size(input))` works fine. – Jonathan Mee Jul 16 '18 at 15:47
  • I agree with Xirema. I have written code that does this with unique pointer while making old code exception safe, but the very first thing I did after that is to use vector. – Tim Seguine Jul 16 '18 at 15:48
  • @JonathanMee Blast, I forgot `make_unique` works with the array form. I just reached for `std::vector` as that is what I normally use. Wan't me to add the `unique_ptr` version to the answer? – NathanOliver Jul 16 '18 at 15:50
3

I assume you need this to fit some interface you have no control of, otherwise I would consider adapting the interface in question to avoid unnecessary creation of temporary data just for the sake of fitting an ill-fitted interface…

Since you just need a temporary array of known size, the simplest solution would probably be to allocate an array of pointers and fill it with pointers to the strings in your vector:

auto pointers = std::make_unique<const char*[]>(size(v));
std::transform(begin(v), end(v), &pointers[0], [](const auto& s) { return s.c_str(); });

This array could also be placed on the stack to avoid dynamic memory allocation. But since you're working with strings here and are willing to copy data into a temporary array, I assume performance is not critical, so I guess there's no need for the added complexity…

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
  • I mean I think that `transform(begin(v), end(v), pointers.get(), [](auto i){ return data(i); })` would be simpler, don't you? But yes this is the answer that I was looking for. – Jonathan Mee Jul 16 '18 at 15:50
  • Gratuitous use of mutable lambdas. I don't like it. – Tim Seguine Jul 16 '18 at 15:51
  • 1
    Indeed, using `std::transform` here is much better. I somehow didn't think of that in the moment I wrote this. I'll update my answer. – Michael Kenzel Jul 16 '18 at 15:53
  • @TimSeguine I mean it's either a lambda or a `for`-loop like in [NathanOliver's answer](https://stackoverflow.com/a/51365504/2642059). I personally prefer the lambda. But that's really just a style choice. – Jonathan Mee Jul 16 '18 at 15:53
  • @Jonathan I have no problem with using a lambda. I have a problem with using a lambda with a mutable capture where none is needed. I prefer the transform version also to a for loop. – Tim Seguine Jul 16 '18 at 15:55
  • @TimSeguine Yeah I agree, my `transform` does look much cleaner. Not to mention that mutable lambdas are far more susceptible to [problems when taken by value](https://stackoverflow.com/q/41108386/2642059) – Jonathan Mee Jul 16 '18 at 15:57
  • OK one more suggested change and then I'll leave you alone. I'd suggest `pointers.get()` instead of `&pointers[0]` – Jonathan Mee Jul 16 '18 at 16:04
  • I usually use `&pointers[0]` because it's more generic. It does not rely on `pointers` being an object of user-defined type that implements a `.get()` method. You can plug in any contiguous array type and it will work…not that that's super essential in this particular case, of course… – Michael Kenzel Jul 16 '18 at 16:09
1

two approaches, depending on whether the c interface requires null termination or not:

#include <vector>
#include <string>
#include <algorithm>

auto make_c_interface_null_terminated(std::vector<std::string> const &input) -> std::vector<const char*>
{
    auto result = std::vector<const char*>(input.size() + 1);
    auto to_c_str = [](auto&& str) { return str.c_str(); };
    std::transform(begin(input), end(input), begin(result), to_c_str);
    // implied:  result[result.size() - 1] = nullptr
    return result;
}

auto make_c_interface(std::vector<std::string> const &input) -> std::vector<const char*>
{
    auto result = std::vector<const char*>(input.size());
    auto to_c_str = [](auto&& str) { return str.c_str(); };
    std::transform(begin(input), end(input), begin(result), to_c_str);
    return result;
}


extern "C" void c_interface_requires_null(const char** argv);
extern "C" void c_interface_sized(size_t size, const char** args);


void test(std::vector<std::string> const &input)
{
    auto output1 = make_c_interface_null_terminated(input);
    c_interface_requires_null(output1.data());

    auto output2 = make_c_interface(input);
    c_interface_sized(output1.size(), output1.data());
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Is the `auto&&` the right choice for the parameter lambda? Since the `str.c_str()` has a life-span associated with the owning string (which had better not mutate or destruct in the interim). – Eljay Jul 16 '18 at 18:42
  • 1
    @Eljay `auto&&` is always the correct type for a lambda's arguments. It uses the same type deduction process as template function argument type deduction. In this case it will deduce to `std::string const&` because `input` is a reference to const. – Richard Hodges Jul 16 '18 at 19:23