Can a structure member be saved as a variable?

Viewed 76

I have a switch statement that based on the user's input will sort an array based on array[i].name or sort by array[i].mark

    switch(input){
        case 1:
            for(int i = 1; i < size; i++){
                structure_name choice = Array[i];
                int j = i - 1;
                while(j >= 0 && tolower(choice.name[0]) <= tolower(Array[j].name[0])){
                    Array[j + 1] = Array[j];
                    j = j - 1;
                }
                Array[j+1] = choice;
            }
            break;
        case 2:
            for(int i = 1; i < size; i++){
                structure_name choice = Array[i];
                int j = i - 1;
                while(j >= 0 && choice.mark <= Array[j].mark){
                    Array[j + 1] = Array[j];
                    j = j - 1;
                }
                Array[j+1] = choice;
            }
            break;
        default:
            break;
    }

As you can see, I'm performing a selection sort twice. Is it possible to perform something like...

    if(input == 1){
        option = .name
    }else if(input == 2){
        option = .mark
    }

So that I can use just one sort and change what variable it's taking from the array. Note: I cant use sort(), I have to use the sort algorithm I've written out. Hence the dilemma of me trying to avoid writing essentially the same code twice.

4 Answers

I think you can use different comparator functions as "options". And give the desired comparator to a sorting function, whether you use std::sort or a custom handmade one.

Considering the following struct (which you forgot to specify):

struct MyStruct
{
    std::string name;
    int mark;
};

you can define your "options" as comparator functions:

int mark_cmp(const MyStruct & lhs, const MyStruct & rhs)
{
    return lhs.mark < rhs.mark; // Compare by mark
}
int name_cmp(const MyStruct & lhs, const MyStruct & rhs)
{
    return lhs.name.compare(rhs.name); // Compare by name
}

Then you could simply write:

int main()
{
    unsigned int input = 1;

    MyStruct arr[5] {{"foo", 5}, {"bar", 6}, {"baz", -9}, {"foobar", 0}, {"foobaz", -3}};

    switch(input)
    {
        case 1: std::sort(arr, arr+5, &name_cmp); // input == 1 ? sort by name
            break;
        case 2: std::sort(arr, arr+5, &mark_cmp); // input == 2 ? sort by mark
            break;
        default:;
    }

    return 0;
}

Note: You don't have to break after the last statement in the switch (in your case, after the default, no need to break).

Note2: I used a raw array to be closer as what you did but I would advise you to use a std::array instead. Or a std::vector if you need a dynamic container.


If you cannot use std::sort, you can create your own sorting function which takes the comparator function as parameters and it will work too.

It's simple if those fields have same type. Note , in C++ proper term is member variable, not field. You can use pointer to member.

struct structure_name {
    std::string name;
    std::string mark;
};

int main()
{
    structure_name S  = { "name", "test"};

    // p now points at name field
    // p can point at any std::string in structure_name 
    std::string structure_name::* p  = &structure_name::name;

    // p is dereferenced using  object S.
    std::cout << (S.*p) << std::endl;
}

In your case option must be such pointer

If fields have different types, some type erasure method should be used. There are two dereference operators, .* and ->*.

if sort parameter is know statically you can template the sort algorithm, using fields type as a template parameter. SO you'll have it written once, but instantiated twice, for each field.

Instead of having a duplicate of the sorting algorithm, you could have it just once and put the input comparison inside the sorting algorithm. Something similar to what you're trying to do would be to determine the correct comparator and pass that on to a general sorting function. If there aren't any requirements that you didn't mention, I would just use std::sort for that:

#include <iostream>
#include <array>
class foo {
public:
    int mark;
    std::string name;
};
int main() {
    std::array<foo, 100> Array;
    // initialize Array here
    int input;
    std::cin >> input;
    if (input == 1) {
        std::sort(Array.begin(), Array.end(), [](foo& a, foo& b) {
            return a.mark > b.mark;
        });
    }
    else if (input == 2) {
        std::sort(Array.begin(), Array.end(), std::less<int>());
    }
    else
        std::cout << "Invalid input of " << input << ", did not sort." << std::endl;
}

I think you could do something like this:

auto f1 = [](fix_type name1, fix_type name2) {
 return tolower(choice.name[0]) <= tolower(Array[j].name[0]);
}

auto f2 = [](fix_type name1, fix_type name2) {
 return choice.mark <= Array[j].mark;
}

std::function<bool(fix_type, fix_type)> f;

if(input == 1){
  f = f1;
}else if(input == 2){
  f = f2;
}
Related