atis
atis

Reputation: 931

Converting std::vector container into an std::set using std::transform

More specifically I have a vector of some struct

std::vector<SomeStruct> extensions = getThoseExtensions();

where someStructVariable.extensionName returns a string.

And I want to create a set of extensionName, something like this std::set<const char*>.

Process is fairly straightforward when done using some for loops but I want to use std::transform from <algorithm> instead.


std::transform has four parameters.

1,2. First range (to transform first range from and to)

3. Second range/inserter (to transform second range)

4. A function


This is what I have so far

auto lambdaFn = 
    [](SomeStruct x) -> const char* { return x.extensionName; };

 std::transform(availableExtensions.begin(),
                availableExtensions.end(),
                std::inserter(xs, xs.begin()),
                lambdaFn);

because there's no "proper context" for std::back_inserter in std::set I'm using std::inserter(xs, xs.begin()).


The problem is I'm trying to return stack mem in my lambda function. So how do I get around this problem?

Oddly enough if I remove return from the function it works just like I expect it to! But I don't understand why and that strikes fear of future repercussion.


EDIT:

I'm using several structs in place of SomeStruct like VkExtensionProperties defined in vulkan_core

typedef struct VkExtensionProperties {
    char        extensionName[VK_MAX_EXTENSION_NAME_SIZE];
    uint32_t    specVersion;
} VkExtensionProperties;

From Khronos specs

Upvotes: 4

Views: 1581

Answers (3)

n314159
n314159

Reputation: 5095

One way to do what you want is with the following lambda

auto lambda = [](const SomeStruct& x) -> const char* { return x.extensions.data();};

The problem with this is, that you are saving pointers to memory owned by someone else (those strings). When they are destroyed (this seems to be the case at the end of the function), the pointer will be dangling. You can get around this by allocating memory in your lambda and copying the data:

auto lambda = [](const SomeStruct & x) -> const char* {
    char* c = new char[x.extensions.length()+1]; 
    std::strcpy(c, x.extensions.data()); 
    return c;
}

But then you have to do memory management yourself (i.e. remember to free those const char*). And that is a bad idea. You should probably reconsider what you are doing. Why are you using const char* here and not std:: string?

Please remember that the typical use for const char* is to save string literals i C-code, i.e. the code

const char* str = "Hello World!";

creates a char array of sufficient size in the static section of the memory, initializes it with the string (a compile time constant) and then saves a pointer to that in str. This is also why this has to be a const char* since another pointer refering to an equal string literal may (or may not) point the exactly the same char array and you don't want to enable change there. So don't just use const char* because you see strings in C saved in those const char* without anyone needing to free them later.

Upvotes: 1

Alan Birtles
Alan Birtles

Reputation: 36614

You probably can't create a set of char * unless all instances of extensionName with the same value point to the same char array (it would store unique pointers instead of unique values). If you use std::set<std::string> instead this will both work and only store unique values and solve your variable lifetime problem as std::string takes care of copying (or moving) itself for you where necessary:

auto lambdaFn = 
    [](const SomeStruct& x) { return std::string(x.extensionName); };
std::set<std::string> xs;
std::transform(availableExtensions.begin(),
                availableExtensions.end(),
                std::inserter(xs, xs.begin()),
                lambdaFn);

Upvotes: 7

Tanveer Badar
Tanveer Badar

Reputation: 5530

There are a couple of things you can do here.

  1. If you own the definition of SomeStruct, it is best if you changed that member to std::string.
  2. Short of that, see if you lambda can take by-ref parameter const auto& obj. This will not create a copy and point back to the object the container has. However, I am still afraid of this solution since this smells like bad class design where ownership and lifetime of members is ambiguous.

Upvotes: 0

Related Questions