5

I need to call the getList function below which comes from a library I cannot change.

#include <iostream>
#include <vector>
#include <string>

//function already exists in a separate library, can't be changed
void getList(const char* list[], int count){};

int main()
{
    std::vector<std::string> vectorOfStrings = {"123" , "abc", "def", "456"};
    
    //call getList and pass vectorOfStrings and vectorOfStrings.size()
    getList(/*convert std::vector<std::string> to const char** */, vectorOfStrings.size());
    
    return 0;
}

I already asked a similar question here and got an answer but I thought there might be a C++ way of doing this.

2
  • 1
    Do not tag both C and C++ except when… oh, okay, please proceed. Commented Dec 15, 2022 at 15:14
  • I afraid you need to write it yourself. 1. Allocate the array of pointers to char. 2. Iterate and assign chPtrArray[n] = vectorOfString[n].data(). Commented Dec 15, 2022 at 15:23

4 Answers 4

6

Use a std::vector<const char*> to store the pointers of the string data stored in the original std::vector<std::string>.

This is a fast operation because you're copying the original data but mind that: this requires that the const char* passed to the function are not stored anywhere because they'll become INVALID once the original string is deallocated (you'll get undefined behavior).

std::vector<std::string> strings = { "foo", "bar", "baz" };
std::vector<const char*> cstrings;
cstrings.reserve(strings.size());

std::transform(
    strings.begin(), 
    strings.end(), 
    std::back_inserter(cstrings), 
    [] (const auto& string) { return string.c_str();}
);

getList(cstrings.data(), cstrings.size());
Sign up to request clarification or add additional context in comments.

2 Comments

I like the use of stl. Th syntax is a bit nicer withstd::ranges::transform in C++20/C++23
Prior to C++23, you can still simplify this a little by removing the back_inserter, eg: std::vector<const char*> cstrings(strings.size()); std::transform(strings.begin(), strings.end(), cstrings.begin(), [] (const auto& string) { return string.c_str(); } );
4

Given that the vector elements are not a C type, you'll need to construct an array to pass to the function:

const char **a = new const char *[vectorOfStrings.size()];
for (int i=0;i<vectorOfStrings.size();i++) {
    a[i] = vectorOfStrings[i].c_str();
}
getList(a, vectorOfStrings.size());
delete[] a;

Or, in a more C++ way:

std::vector<const char *> v2;
for (int i=0;i<vectorOfStrings.size();i++) {
    v2.push_back(vectorOfStrings[i].c_str());
}
getList(v2.data(), vectorOfStrings.size());

2 Comments

It will gove const char *, I believe. IMO .data() will be better
@EricPostpischil since C++11, string.c_str() and string.data() are required to return a pointer to the same null-terminated memory. The difference being that .c_str() is a read-only pointer, whereas .data() is read-only up to C++17 and then it is read/write pointer.
0

For those who also deal with lack of std::transform in pre-C++17-20 standards: in continuation of the excellent solution https://stackoverflow.com/a/74813820/30971218, I am attaching my own functions with 'for-each' loop:

std::vector<char *> conv2vect (std::vector<std::string> const & inp_vect)
{ 
    std::vector<char *> outp_vect; 
    
    for (auto const & el : inp_vect)
    { 
        outp_vect.push_back(const_cast<char *>(el.c_str()));
    } 
    
    return outp_vect;
}

This fine suitable for C API functions where you need to provide a non-const Pointer-2-Pointer (if you need a std::vector<char const *> for char const ** input arg, simple remove a const_cast<>; I used it only because std::string::data(), as std::string::c_str() (as described in https://stackoverflow.com/questions/74813679/how-to-pass-a-vector-of-strings-to-a-function-with-a-c-style-pointer-to-c-string#comment132036577_74813820), returns const internal buffer before C++17). Note: because of 'for-each' loop specific, iterator must be reference; in case of by-value access (auto el : inp_vect) output will be garbage, because of destruction of std::string's temporary objects, and, as a consequence, internal char array (requested with str->c_str()).

Const-analogue (if char const ** needed):

std::vector<char const *> conv2vect_const (std::vector<std::string> const & inp_vect)
{ 
    std::vector<char const *> outp_vect; 
    
    for (auto const & el : inp_vect)
    { 
        outp_vect.push_back(el.c_str());
    } 
    
    return outp_vect;
}

And lambda-analogue:

auto conv2vect_lambda = []
( std::vector<std::string> const & inp_vect )
 -> std::vector<char *>
{
    std::vector<char *> outp_vect;

    for (auto const & el : inp_vect)
    {
        outp_vect.push_back(const_cast<char *>(el.c_str()));
    }

    return outp_vect;
};

For 'more' C++ and RAII-principles fit, to emphasize of that we deal with references on temporaries in our 'converted' vector, we can use a RAII smart-pointer return:

std::unique_ptr<std::vector<char *>> conv2smart_ptr (
        std::vector<std::string> const & inp
        )
{
    // before C++14 and 'make_unique'..
    std::vector<char *> * tv = new std::vector<char *>{};

    for (auto const & el : inp){
        tv->push_back(const_cast<char *>(el.c_str()));
    }

    return std::unique_ptr<std::vector<char *>>{tv};
}

Call example:

void foo(char ** pp, size_t size_of_pp); 
void foo_const(char const ** pp, size_t size_of_pp); // some 'C' functions

std::vector<std::string> vect{"abc","def"}; // input vector to 'converting'

//1
std::vector<char *> v = conv2vect(vect);
foo(v.data(), v.size());
//2
std::vector<char const *> v_const = conv2vect_const(vect);
foo_const(v_const.data(), v_const.size());
//3
v = conv2vect_lambda(vect);
foo(v.data(), v.size());
//4
std::unique_ptr<std::vector<char *>> smart_ptr_v = conv2smart_ptr(vect);
foo(smart_ptr_v->data(), smart_ptr_v->size());

Comments

0

Here's a very simple and wonderful solution without using any explicit for loop, algorithm or lambda expression that many programmers don't even know about.

There will be used an iterator adapter and the following standard constructor of the class template std::vector

template constexpr vector(InputIterator first, InputIterator last, const Allocator& = Allocator());

The constructor itself will do all the required actions: reserving the required extent of memory and inserting all elements.

Here you are.

    std::vector<std::string> vectorOfStrings = { "123" , "abc", "def", "456" };

    //  There is used a code block to destroy the vector of pointers 
    //  to C strings automatically
    {
        struct It : std::vector<std::string>::const_iterator
        {
            It( const std::vector<std::string>::const_iterator &it )
                : std::vector<std::string>::const_iterator( it )
            {
            }

            const char * operator *() const noexcept
            {
                return std::vector<std::string>::const_iterator::operator *().c_str();
            }
        };

        std::vector<const char *> vectorOfCStrings( It( std::cbegin( vectorOfStrings ) ),
                     It( std::cend( vectorOfStrings ) ) );
        
        getList( vectorOfCStrings.data(), static_cast<int>( std::size( vectorOfCStrings ) ) );
    }

Below there is a demonstration program that shows that approach in action. For simplicity the function getList just outputs the passed array.

#include <string>
#include <iterator>
#include <vector>
    
#include <cstdio>
    
extern "C"
{
    void getList( const char * list[], int count )
    {
        for ( int i = 0; i < count; i++ )
        {
            std::printf( "%s ", list[i] );
        }
            
        std::putchar( '\n' );
    }
}
    
struct It : std::vector<std::string>::const_iterator
{
    It( const std::vector<std::string>::const_iterator &it )
        : std::vector<std::string>::const_iterator( it )
    {
    }

    const char * operator *() const noexcept
    {
        return std::vector<std::string>::const_iterator::operator *().c_str();
    }
};


int main()
{
    std::vector<std::string> vectorOfStrings = { "123" , "abc", "def", "456" };
    
    //  There is used a code block to clear the vector of pointers 
    //  to C strings automatically
    {
        std::vector<const char *> vectorOfCStrings( It( std::cbegin( vectorOfStrings ) ),
                                                    It( std::cend( vectorOfStrings ) ) );
        getList( vectorOfCStrings.data(), static_cast<int>( std::size( vectorOfCStrings ) ) );
    }
}

The program output is

123 abc def 456 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.