0

In some words: how can I pass various fields from a custom class to a single function?

Now in details: I have a std::vector containing a class, for example CustomClass from which I have to extract a result from a field from this class by some criteria which are fields in this class and to combine somehow this data.

My first approach to this problem was to use a function which accepts as a parameter the std::vector of the class in order to extract the data and return a std:map. The key in this map is the type of the criteria by which the data should be combined and the value is an int with the combined data from all members of this vector.

The problem is that the criteria is not only one - more than one field from this class may be used as criteria (let for easiness all of the criteria are std::string, if they are not - I could make the function templated).

The easiest way for me now is to make dozens of functions with almost identical code and each of them to extract a simple concrete field from this class. However changes might require similar changes to all of the dozens of functions which would be a maintenance headache. But in this stage I cannot think how to pass to a single function a field from this class...

Here's an example code from this class:

// this is the class with data and criteria
class CustomClass
{
public:
    std::string criteria1;
    std::string criteria2;
    std::string criteria3;
//... and others criteria
    int dataToBeCombined;
// other code
};

// this is one of these functions
std::map<std::string, int> getDataByCriteria1(std::vector<CustomClass> aVector)
{
    std::map<std::string, int> result;
    foreach(CustomClass anObject in aVector)
    {
        if(result.find(anObject.criteria1)==result.end()) // if such of key doesn't exists
        {
            result.insert(std::make_pair(anObject.criteria1, anObject.dataToBeCombined));
        }
        else
        {
            // do some other stuff in order to combine data
        }
    }
    return result;
}

and by similar way I should make the other functions which should work with CustomClass::criteria2, CustomClass::criteria3, etc.

I thought to make these criteria in a single array and to pass to this function only the number of the criteria but the class will be used by others for other purposes and the fields must be easy to read, so this will not be an option (i.e. the real names are not criteria1, criteria2, etc. but are descriptive).

Anyone with ideas?

EDIT: Someone referred my question to "C++ same function parameters with different return type" which obviously is very different - the function in my case return the same type every time, just the parameters it takes must be various fields from a class.

winsett
  • 71
  • 7
  • 9
    This question is full of words that are valid words in English, but combined in a way that does not make sense. "I have an std::vector from a class". "this vector passes it through". "it might be used more than one fields from this class as criteria". This question needs to be rewritten so that it's more clear, and probably half of its current size. – Sam Varshavchik Jul 11 '16 at 12:18
  • "Here's an example code from this class:" clearly not because `foreach(CustomClass anObject in aVector)` is not C++. Also, sorry, but this post is word salad. Please explain, step-by-step, using short paragraphs/bullet points, what you are trying to do - preferably with specific references to parts of your code, which should actually be C++, should be present in its entirety, and should show an example of input vs desired output data. – underscore_d Jul 11 '16 at 12:22
  • Possible duplicate of [C++ same function parameters with different return type](http://stackoverflow.com/questions/14840173/c-same-function-parameters-with-different-return-type) – Jonathan Mee Jul 11 '16 at 12:48
  • It sounds like what you want to do is to apply an algorithm to a each element of a `std::vector` in order to generate a result of some kind. It sounds like a fold and reduce operation, something like [How to fold STL container?](http://stackoverflow.com/questions/3906796/how-to-fold-stl-container) or perhaps [Functional Programming in C++11](http://www.grimm-jaud.de/images/stories/pdfs/FunctionalProgrammingInC%2B%2B11.pdf) – Richard Chambers Jul 11 '16 at 18:11
  • See as well http://stroustrup.com/Programming/21_algorithms.ppt which is a PowerPoint slide package from Bjarne Stroustrup on The STL (maps and algorithms). – Richard Chambers Jul 11 '16 at 18:17

5 Answers5

1

You can use pointer to member. Declare an argument std::string CustomClass::*pField in your function, pass it with &CustomClass::criteriaN, access it with anObject.*pField.

See more on the topic: Pointers to data members.

Mikhail
  • 20,685
  • 7
  • 70
  • 146
1

If all "criteria" are of the same type, I don't see an elegant solution but you can "enumerate" they in some way and use their number.

By example, you can declare a templated getVal() method in CustomClass in this way

template <int I>
   const std::string & getVal () const;

and implement they, number by number, criteria by criteria, in this way (outside the body of the class)

template <>
const std::string & CustomClass::getVal<1> () const
 { return criteria1; }

template <>
const std::string & CustomClass::getVal<2> () const
 { return criteria2; }

template <>
const std::string & CustomClass::getVal<3> () const
 { return criteria3; }

Now, you can transform getDataByCriteria1() in a templated function getDataByCriteria() in this way

template <int I>
std::map<std::string, int> getDataByCriteria (std::vector<CustomClass> aVector)
 {
   std::map<std::string, int> result;

   for (const auto & cc : aVector)
    {
      if ( result.find(cc.getVal<I>()) == result.end()) // if such of key doesn't exists
       {
         result.insert(std::make_pair(cc.getVal<I>(), cc.dataToBeCombined));
       }
      else
       {
            // do some other stuff in order to combine data
       }
    }

   return result;
 }

and call it in this way

auto map1 = getDataByCriteria<1>(ccVec);
auto map2 = getDataByCriteria<2>(ccVec);
auto map3 = getDataByCriteria<3>(ccVec);

--- EDIT: added solution (C++14 only) for different types criteria ---

A little different if the "criteria" are of different types.

The solution work but in C++14, thanks to auto and decltype().

By example, if

std::string criteria1;
int         criteria2;
long        criteria3;

You can declare getVal() with auto

template <int I>
   const auto & getVal () const;

and define (with auto) all versions of getVal()

template <>
const auto & CustomClass::getVal<1> () const
 { return criteria1; }

template <>
const auto & CustomClass::getVal<2> () const
 { return criteria2; }

template <>
const auto & CustomClass::getVal<3> () const
 { return criteria3; }

and combining auto with decltype(), you can modify getDataByCriteria() in this way

template <int I>
auto getDataByCriteria (std::vector<CustomClass> aVector)
 {
   std::map<decltype(aVector[0].getVal<I>()), int> result;

   for (const auto & cc : aVector)
    {
      if ( result.find(cc.getVal<I>()) == result.end()) // if such of key doesn't exists
       {
         result.insert(std::make_pair(cc.getVal<I>(), cc.dataToBeCombined));
       }
      else
       {
            // do some other stuff in order to combine data
       }
    }

   return result;
 }

The use of the function remain the same (thanks to auto again)

auto map1 = getDataByCriteria<1>(ccVec);
auto map2 = getDataByCriteria<2>(ccVec);
auto map3 = getDataByCriteria<3>(ccVec);

p.s.: caution: code not tested

p.s.2 : sorry for my bad English

max66
  • 65,235
  • 10
  • 71
  • 111
  • Why not template on `enum { CRITERIA1, CRITERIA2, ...}` instead of int? Then you could use `getDataByCriteria(ccVec)` (with actual criteria name used in place of `CRITERIA1`) etc instead of magic numbers. – user4815162342 Jul 14 '16 at 06:56
0

You tagged it C++11, so use variadic templates.

            class VariadicTest
            {
            public:
                VariadicTest()
                {
                    std::map<std::string, int> test1 = getDataByCriteria(testValues, criteria1);
                    std::map<std::string, int> test2 = getDataByCriteria(testValues, criteria2);
                    std::map<std::string, int> test3 = getDataByCriteria(testValues, criteria1, criteria2);
                    std::map<std::string, int> test4 = getDataByCriteria(testValues, criteria1, criteria3);
                }

            private:
                std::string criteria1 = { "Hello" };
                std::string criteria2 = { "world" };
                std::string criteria3 = { "." };

                std::vector<CustomClass> testValues = { {"Hello",1}, {"world",2},{ "!",3 } };

                template<typename T> std::map<std::string, int> getDataByCriteria(std::vector<CustomClass> values, T criteria)
                {
                    std::map<std::string, int> result;
                    //do whatever is needed here to filter values
                    for (auto v : values)
                    {
                        if (v.identifier == criteria)
                        {
                            result[values[0].identifier] = values[0].value;
                        }
                    }
                    return result;
                }

                template<typename T, typename... Args> std::map<std::string, int> getDataByCriteria(std::vector<CustomClass> values, T firstCriteria, Args... args)
                {
                    std::map<std::string, int> result = getDataByCriteria(values, firstCriteria);
                    std::map<std::string, int> trailer = getDataByCriteria(values, args...);
                    result.insert(trailer.begin(), trailer.end());
                    return result;
                }
            };
Matthias Bäßler
  • 480
  • 1
  • 5
  • 11
0

You can use a function to extract a filed such as

std::string extractFiled(const CustomClass &object, int which) {
  switch (which) {
    case 1:
      return object.criteria1;
    case 2:
      return object.criteria2;
    case 3:
      return object.criteria3;
    default:
      return object.criteria1;
  }
}

and getDataByCriteria add an arg to indicate which filed to use. Or you can just use macro to implement getDataByCriteria.

phyxnj
  • 647
  • 5
  • 11
  • He wants to return a different type of `map` based on the value of `criteria1`. This always returns the same value. – Jonathan Mee Jul 11 '16 at 12:45
  • @JonathanMee, According to `I though about to make these criteria in a single array and to pass to this function only the number of the criteria` that the questioner said, all fileds have the same type. Any way, the answer of max66 is better. – phyxnj Jul 11 '16 at 12:59
  • Right after your quote he says: "So this will not be an option" What he was saying is he knew how to do what you've provided and it doesn't solve the problem. – Jonathan Mee Jul 11 '16 at 13:04
  • Thanks @phyxnj, that is what I was looking for! First tried (too optimistically) the max66's guidance with the auto version he proposed but the compiler thrown a bunch of errors and leaved it - I had no patience for resolving all the errors. Your method is simple which I like and it uses no other additional includes. Thanks to everyone else who joined the discussion! – winsett Jul 13 '16 at 15:32
0

You do not specify the actual operations to be done under the various conditions of the criteria being met so it is hard to say how much they actually can be combined.

Here is a possible solution using the std::accumulate() of the STL along with some additional functionality. This example was compiled with Visual Studio 2015.

This approach would make sense if most of the functionality can be combined into a reasonably small accumulation function because most of the criteria are handled in the same way. Or you could have the accumulate_op() function call other functions for specific cases while handling the general case itself.

You might take this as a beginning and make the appropriate modifications.

One such modification may be to get rid of the use of std::map to maintain state. Since using this approach you would iterate through the std::vector doing the accumulation based on the criteria, I am not sure you would even need to use std::map to remember anything if you are accumulating as you go.

// map_fold.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <numeric>

// this is the class with data and criteria
class CustomClass
{
public:
    CustomClass() : dataToBeCombined(0) {}
    std::string criteria1;
    std::string criteria2;
    std::string criteria3;
    //... and others criteria
    int dataToBeCombined;
    // other code
};

// This is the class that will contain the results as we accumulate across the
// vector of CustomClass items.
class Criteria_Result {
public:
    Criteria_Result() : dataToBeCombined(0) {}
    CustomClass myCriteria;
    std::map<std::string, int> result1;
    std::map<std::string, int> result2;
    std::map<std::string, int> result3;

    int dataToBeCombined;
};

// This is the accumulation function we provide to std::accumulate().
// This function will build our results.
class accumulate_op {
public:
    Criteria_Result * operator ()(Criteria_Result * x, CustomClass &item);

};

Criteria_Result * accumulate_op::operator ()(Criteria_Result *result, CustomClass &item)
{

    if (!result->myCriteria.criteria1.empty() && !item.criteria1.empty()) {
        std::map<std::string, int>::iterator it1 = result->result1.find(item.criteria1);
        if (it1 == result->result1.end()) // if such of key doesn't exists
        {
            result->result1.insert(std::make_pair(item.criteria1, item.dataToBeCombined));
        }
        else
        {
            // do some other stuff in order to combine data
            it1->second += item.dataToBeCombined;
        }
        result->dataToBeCombined += item.dataToBeCombined;
    }

    if (!result->myCriteria.criteria2.empty() && !item.criteria2.empty()) {
        std::map<std::string, int>::iterator it2 = result->result2.find(item.criteria2);
        if (it2 == result->result2.end()) // if such of key doesn't exists
        {
            result->result2.insert(std::make_pair(item.criteria2, item.dataToBeCombined));
        }
        else
        {
            // do some other stuff in order to combine data
            it2->second += item.dataToBeCombined;
        }
        result->dataToBeCombined += item.dataToBeCombined;
    }

    if (!result->myCriteria.criteria3.empty() && !item.criteria3.empty()) {
        std::map<std::string, int>::iterator it3 = result->result3.find(item.criteria3);
        if (it3 == result->result3.end()) // if such of key doesn't exists
        {
            result->result3.insert(std::make_pair(item.criteria3, item.dataToBeCombined));
        }
        else
        {
            // do some other stuff in order to combine data
            it3->second += item.dataToBeCombined;
        }
        result->dataToBeCombined += item.dataToBeCombined;
    }

    return result;
}

int main()
{
    Criteria_Result  result;
    std::vector<CustomClass> aVector;

    // set up the criteria for the search
    result.myCriteria.criteria1 = "string1";
    result.myCriteria.criteria2 = "string2";

    for (int i = 0; i < 10; i++) {
        CustomClass xx;

        xx.dataToBeCombined = i;
        if (i % 2) {
            xx.criteria1 = "string";
        }
        else {
            xx.criteria1 = "string1";
        }
        if (i % 3) {
            xx.criteria2 = "string";
        }
        else {
            xx.criteria2 = "string2";
        }

        aVector.push_back (xx);
    }

    // fold the vector into our results.
    std::accumulate (aVector.begin(), aVector.end(), &result, accumulate_op());

    std::cout << "Total Data to be combined " << result.dataToBeCombined << std::endl;

    std::cout << " result1 list " << std::endl;
    for (auto jj : result.result1) {
        std::cout << "    " << jj.first << "   " << jj.second << std::endl;
    }
    std::cout << " result2 list " << std::endl;
    for (auto jj : result.result2) {
        std::cout << "    " << jj.first << "   " << jj.second << std::endl;
    }
    std::cout << " result3 list " << std::endl;
    for (auto jj : result.result3) {
        std::cout << "    " << jj.first << "   " << jj.second << std::endl;
    }

    std::cout << " Trial two \n\n" << std::endl;

    result.myCriteria.criteria2 = "";
    result.result1.clear();
    result.result2.clear();
    result.result3.clear();
    result.dataToBeCombined = 0;

    // fold the vector into our results.
    std::accumulate(aVector.begin(), aVector.end(), &result, accumulate_op());

    std::cout << "Total Data to be combined " << result.dataToBeCombined << std::endl;

    std::cout << " result1 list " << std::endl;
    for (auto jj : result.result1) {
        std::cout << "    " << jj.first << "   " << jj.second << std::endl;
    }
    std::cout << " result2 list " << std::endl;
    for (auto jj : result.result2) {
        std::cout << "    " << jj.first << "   " << jj.second << std::endl;
    }
    std::cout << " result3 list " << std::endl;
    for (auto jj : result.result3) {
        std::cout << "    " << jj.first << "   " << jj.second << std::endl;
    }

    return 0;
}

This produces the output as follows:

Total Data to be combined 90
 result1 list
    string   25
    string1   20
 result2 list
    string   27
    string2   18
 result3 list
 Trial two


Total Data to be combined 45
 result1 list
    string   25
    string1   20
 result2 list
 result3 list
Richard Chambers
  • 16,643
  • 4
  • 81
  • 106