Can someone give me idea on this problem. I have searched on internet about this, but couldn't get much info as I wished to have.
Say there is a class.
class Foo {
explicit Foo() {}
int getVar1();
int getVar2();
void setVar1(int v);
void setVar2(int v);
private:
int var1, var2;
};
now given a list of tokens {"var1", "var2", ... "varN"}, is there any way I can create the function name at runtime and call those member functions of some object of type Foo. like for e.g
Foo obj;
string input = "Var1,Var2,Var3,...VarN";
vector<string> tokens = splitString(input);
for (vector<string>::const_iterator it = tokens.begin(); it != tokens.end(); ++it) {
string funName = "get" + *it;
// somehow call obj.getVar1()....obj.getVarN()
}
using if else is fine for small numbers of variables, but its not good for large number of variables. Using bind and functors also doesn't solve this. One webpage suggested making memory executable at runtime and then using reinterpret_cast, I don't know whether this would work.
UPDATE
Ok, as from the answers and other searches on internet, I see that there is not elegant way of doing this in C++. There is no reflection in C++ as of now. All hacks would require compile time resolution of member function pointers.
Could someone give me ideas on alternate class design in these scenario when you have lots of variables and setters and getters functions...or whether getters and setters are good practice in c++ ?
As an idea consider the following code
struct A
{
void f1() { std::cout << "A::f1()\n"; }
void f2() { std::cout << "A::f2()\n"; }
void f3() { std::cout << "A::f3()\n"; }
void f4() { std::cout << "A::f4()\n"; }
};
std::map<std::string, void( A::* )()> m = { { "f1", &A::f1 }, { "f2", &A::f2 }, { "f3", &A::f3 }, { "f4", &A::f4 } };
A a;
for ( auto p : m ) ( a.*p.second )();
You can make the map as a data member of your class.
You can't "add" members at runtime. C++ is strongly typed at compile time.
You can get the behaviour you want by having a map<string, func_type> and using it to resolve your string to an actual function. You can create it using macros to make sure that the string names match the function names.
#DEFINE ADD_METHOD(map_var, func) map_var["func"] = &func
A simple/not perfect solution could be to use a intermediate methods checking the parameter and calling the getVar* method accordingly.
An example like this one maybe:
class Foo
{
public:
explicit Foo() {}
int getVar1() { return 1; }
int getVar2() { return 2; }
void setVar1(int v) { var1 = v; }
void setVar2(int v) { var2 = v; }
int callGetVar(const std::string &var)
{
if (var == "Var1") return getVar1();
if (var == "Var2") return getVar2();
else { return -1; }
}
private:
int var1, var2;
};
int main()
{
Foo obj;
std::string input = "Var1,Var2,Var3,...VarN";
std::vector<std::string> tokens = { "Var1", "Var2", "Var2", "Var1", "Var1", "Var2", "Var2", "Var1"};
auto tokensIT = tokens.begin();
for (; tokensIT != tokens.end(); ++tokensIT)
{
// somehow call obj.getVar1()....obj.getVarN()
std::cout << obj.callGetVar(*tokensIT);
}
return 0;
}
why not look at it in a referent way:
For each variable assign an index number, starting from 0, 1, 2....
You keep this values in a map (key is the variable name, value is the assigned value).
All the values of those variables, you keep in an array, so that the value of the first variable in in cell 0, the next one is in cell 1 etc.
so, when you want to get/set value, all you need to do, is, find it's index in the map, and access the relevant cell in vector.
You can try this
one example:
template<class C1, class C2, class R, class... A, std::size_t... I>
boost::json::value
call_impl_(C1& c1, R(C2::* pmf)(A...), boost::json::array const& args,
std::index_sequence<I...>)
{
return boost::json::value_from(
(c1.*pmf)(boost::json::value_to< boost::remove_cv_ref_t<A> >(args[I])...));
}
template<class C1, class C2, class R, class... A>
boost::json::value
call_impl(C1& c1, R(C2::* pmf)(A...), boost::json::array const& args)
{
if (args.size() != sizeof...(A))
{
throw std::invalid_argument("Invalid number of arguments");
}
return call_impl_(c1, pmf, args, std::index_sequence_for<A...>());
}
template<class C>
boost::json::value
call(C& c, boost::string_view method, boost::json::value const& args)
{
using Fd = boost::describe::describe_members<C,
boost::describe::mod_public | boost::describe::mod_function>;
bool found = false;
boost::json::value result;
boost::mp11::mp_for_each<Fd>([&](auto D) {
if (!found && method == D.name)
{
result = call_impl(c, D.pointer, args.as_array());
found = true;
}
});
if (!found)
{
throw std::invalid_argument("Invalid method name");
}
return result;
}
//test1 from https://github.com/bytemaster/boost_reflect
struct calculator { //need Generic maybe..
int add(int v, int u) { return u + v; }
int sub(int v) { return result_ -= v; }
int result() { return result_; }
private:
int result_ = 0.0;
};
BOOST_DESCRIBE_STRUCT(calculator, (), (add, sub), (result));
int main(int argc, char** argv) {
calculator cal;
std::string line;
std::string cmd;
std::string args;
while (true) {
std::cerr << "Enter Method: ";
std::getline(std::cin, line);
int pos = line.find('(');
cmd = line.substr(0, pos);
args = line.substr(pos + 1, line.size() - pos - 2);
std::cout << "args: " << args << std::endl;
std::vector<std::string> num_str;
boost::split(num_str, args, boost::is_any_of(","));
std::vector<int> nums;
std::for_each(num_str.begin(), num_str.end(), [&](std::string str) {nums.push_back(std::stoi(str)); });
// Convert the vector to a JSON array
const boost::json::value jv = boost::json::value_from(nums);
std::cout << call(cal, cmd, jv) << std::endl;
}
return 0;
}
It can be passed under visual studio 2022 c++17.
with cpp20 it will report an error, I don’t know why
Related
typedef void (*void_proc)(void* parameter);
void* parallel_init(void* dummy, int core_number);
int parallel_addtask(void* parallel_monitor, void_proc process, void *parameter);
int parallel_waittask(void* parallel_monitor, int task_id);
int parallel_uninit(void* parallel_monitor);
struct parallel_parameter {
int end;
int begin;
};
void process(void* parameter) {
auto p = reinterpret_cast<parallel_parameter*>(parameter);
// ur_function_name(p->begin, p-end);
}
above is a parallel library(c style) which i woule like to use. every time u call it, u should define a specific struct parameter, it is so annoying that i want implement a template function to mitigate the call steps and i try some kinds of methods to achieve this but failed.
template<typename _function, typename... _parameter>
int parallel_executor(_function&& function, _parameter&&... parameter) {
auto res = 0;
parallel_parameter p[8]{0};
auto body = [](void* para) -> void {
auto p = reinterpret_cast<parallel_parameter*>(para);
function(p->begin, p->end, std::forward<_parameter>(parameter)...)
};
auto parallel_handle = parallel_init(nullptr, 8);
do {
for (int i = 0;i < 8; ++i) {
res = parallel_addtask(parallel_handle, body, static_cast<void*>(&p[i]));
if (res != 0) break;
}
for (int i = 0; i < 8; ++i) {
res = parallel_waittask(parallel_handle, i);
if (res != 0) break;
}
} while (false);
parallel_uninit(parallel_handle);
return res;
}
this call is just simple to show my dilemma, when i use the parallel_executor, it turns out sessioncannot be accessed, because i am not specific the capture style, but when i change the body into below style, the parallel_addtask will not accept body function.
auto body = [&](void* para) -> void {
auto p = reinterpret_cast<parallel_parameter*>(para);
function(p->begin, p->end, std::forward<_parameter>(parameter)...)
};
and now i am in this awkward position for a while. below is the call style which i prefered.
auto ret = parallel_executor(
[](int begin, int end, int parameter_1, int parameter_2) {
std::cout << begin << " ==> " << end << " ==> " << parameter_1 << std::endl;
},
100, // parameter_1
200 // parameter_2
);
regarding the issue, i hope I have made myself clear. any suggestion is appreciated.
Wrapper might look like:
class ParrallelWrapper
{
public:
ParrallelWrapper(int core_number) :
parallel_monitor(parallel_init(nullptr, core_number))
{}
ParrallelWrapper(const ParrallelWrapper&) = delete;
ParrallelWrapper& operator= (const ParrallelWrapper&) = delete;
~ParrallelWrapper() { parallel_uninit(parallel_monitor); }
int AddTask(std::function<void()> f) {
auto run_function = *[](void* f){
(*reinterpret_cast<std::function<void()>*>(f))();
};
functions.push_back(std::make_unique<std::function<void()>>(f));
return parallel_addtask(parallel_monitor, run_function, functions.back().get());
}
int Wait(int task_id) { return parallel_waittask(parallel_monitor, task_id); }
private:
void* parallel_monitor = nullptr;
// Ensure lifetime, and pointer consistence.
std::vector<std::unique_ptr<std::function<void()>>> functions;
};
Demo
With appropriate blanks for specifying begin, end, and the number of tasks, you can use something like
struct parallel_deleter {
void operator()(void *m) const {parallel_uninit(m);}
};
template<class F,class ...TT>
int parallel_executor(F f,TT &&...tt) {
constexpr auto p=+f; // require captureless
constexpr int n=/*...*/;
std::unique_ptr<void,parallel_deleter> m(parallel_init(nullptr,n));
struct arg {
int begin,end;
std::tuple<TT...> user;
};
std::vector<arg> v(n,{0,0,{tt...}});
for(auto &x : v) {
x.begin=/*...*/;
x.end=/*...*/;
if(const int res=parallel_addtask(m.get(),[](void *v) {
const auto &a=*static_cast<arg*>(v);
std::apply([&a](auto &...aa) {p(a.begin,a.end,aa...);},a.user);
},&x)) return res;
}
for(int i=0;i<n;++i)
if(const int res=parallel_waittask(m.get(),i)) return res;
return parallel_uninit(m.release());
}
This design relies on a captureless lambda being passed (so that p can be used inside the task lambda without capturing anything); if you need to support any callable, Jarod42's solution based on std::function is superior.
Let's say I multiple functions with variable arguments:
void greetWorld() {
cout << "Hello World!" << endl;
}
void greetName(const string& name) {
cout << "Hello " << name << "!" << endl;
}
void printAddition(const int lhs, const int rhs) {
cout << "Addition: " << to_string(lhs + rhs) << endl;
}
And these are stored in a map of std::strings to functions (functions being stored as a polymorphic class).
template<typename... Args>
class DerivedFunction;
class BaseFunction {
public:
template<typename... Args>
void operator()(Args... args) const {
(*static_cast<const DerivedFunction<Args...>*>(this))(args...);
}
};
template<typename... Args>
class DerivedFunction : public BaseFunction {
public:
DerivedFunction(void(*function)(Args...)) {
this->function = function;
}
void operator()(Args... args) const {
function(args...);
}
private:
void(*function)(Args...);
};
template<typename... Args>
unique_ptr<DerivedFunction<Args...>> make_function(
void(*function)(Args...)
) {
return std::make_unique<DerivedFunction<Args...>>(function);
}
int main() {
unordered_map<string, unique_ptr<BaseFunction>> function_map;
function_map.insert({ "greetWorld", make_function(&greetWorld) });
function_map.insert({ "greetName", make_function(&greetName) });
function_map.insert({ "printAddition", make_function(&printAddition) });
...
}
I can call the functions at compile time like:
int main() {
...
(*function_map.at("greetWorld"))();
(*function_map.at("greetName"))("Foo"s);
(*function_map.at("printAddition"))(1, 2);
}
If I then have a string, or stream like:
greetWorld
greetName string Foo
printAddition int 1 int 2
What would be a good way to call the functions?
I can not figure out any way to cast a type at runtime.
Why?
I am trying to implement some kind of remote call procedure for learning purposes. I do not want to use an external library as I am trying to learn how to implement this with the C++ standard library for the sake of understanding C++ more.
What have I tried?
Not much. I've tested creating functions that take a std::vector of std::anys as an argument, and then had the function any_cast them to the type they are. Whilst this does work, it does not look nice, it requires duplicates of all functions, I would rather be able to write functions with meaningful arguments than ambigious.
Minimum Example
#include <iostream>
#include <string>
#include <unordered_map>
#include <memory>
using namespace std;
void greetWorld() {
cout << "Hello World!" << endl;
}
void greetName(const string& name) {
cout << "Hello " << name << "!" << endl;
}
void printAddition(const int lhs, const int rhs) {
cout << "Addition: " << to_string(lhs + rhs) << endl;
}
template<typename... Args>
class DerivedFunction;
class BaseFunction {
public:
template<typename... Args>
void operator()(Args... args) const {
(*static_cast<const DerivedFunction<Args...>*>(this))(args...);
}
};
template<typename... Args>
class DerivedFunction : public BaseFunction {
public:
DerivedFunction(void(*function)(Args...)) {
this->function = function;
}
void operator()(Args... args) const {
function(args...);
}
private:
void(*function)(Args...);
};
template<typename... Args>
unique_ptr<DerivedFunction<Args...>> make_function(
void(*function)(Args...)
) {
return std::make_unique<DerivedFunction<Args...>>(function);
}
int main() {
unordered_map<string, unique_ptr<BaseFunction>> function_map;
function_map.insert({ "greetWorld", make_function(&greetWorld) });
function_map.insert({ "greetName", make_function(&greetName) });
function_map.insert({ "printAddition", make_function(&printAddition) });
cout << "Calling functions at compile time." << endl << endl;
(*function_map.at("greetWorld"))();
(*function_map.at("greetName"))("Foo"s);
(*function_map.at("printAddition"))(1, 2);
//cout << endl << "Calling functions at runtime." << endl << endl;
//string runtime =
// "greetWorld\n"
// "greetName string Foo\n"
// "printAddition int 1 int 2";
//
// todo: call functions
}
Solved.
If you apply the accepted solution, you can call functions from the text like I had wanted.
Here is new code for an example Tcp server and client. The client sends function names and arguments as a string to the server. The server then executes these. Exactly what I wanted.
struct FunctionNameAndArguments {
string function_name;
vector<RPC> arguments;
};
FunctionNameAndArguments parseFunctionNameAndArguments(
const string& function_name_and_arguments_string
) {
istringstream ss(function_name_and_arguments_string);
FunctionNameAndArguments function_name_and_arguments;
// function name
ss >> function_name_and_arguments.function_name;
// arguments
auto& arguments = function_name_and_arguments.arguments;
while (!ss.eof()) {
string function_type;
ss >> function_type;
// integer
if (function_type == "int") {
int value;
ss >> value;
arguments.push_back(value);
}
// string
else if (function_type == "string") {
string value;
ss >> value;
arguments.push_back(value);
}
else {
throw exception("unknown argument type");
}
}
return function_name_and_arguments;
}
int main() {
unordered_map<string, RPCHandler> functions = {
{ "greetWorld", make_invoker(&greetWorld) },
{ "greetName", make_invoker(&greetName) },
{ "printAddition", make_invoker(&printAddition) }
};
char server;
cout << "Server? (y/n): " << endl;
cin >> server;
// server
if (server == 'y') {
// accept client
TcpListener listen;
listen.listen(25565);
TcpSocket client;
listen.accept(client);
size_t received;
// receive size of string
size_t size;
client.receive(&size, sizeof(size), received);
// receive function name and arguments as string
string function_name_and_arguments_string;
function_name_and_arguments_string.resize(size);
client.receive(
function_name_and_arguments_string.data(),
size,
received
);
// go through each line
istringstream lines(function_name_and_arguments_string);
string line;
while (getline(lines, line)) {
// parse function name and arguments
auto [function_name, arguments] = parseFunctionNameAndArguments(
line
);
// call function
functions.at(function_name)(
arguments
);
}
}
// client
else {
// connect to server
TcpSocket server;
server.connect("localhost", 25565);
// function calls string
const string function_calls =
"greetWorld\n"
"greetName string Foo\n"
"printAddition int 1 int 2";
size_t size = function_calls.size();
// send size of string
server.send(&size, sizeof(size));
// send function calls string
server.send(function_calls.data(), size);
}
}
Let us assume you have a list of types (taking int and string as an example) usable in RPC, we can combine them in a RPC type and associated RPCHandler as follows:
using RPC = std::variant<int, std::string>;
using RPCHandler = std::function<void(std::vector<RPC>)>;
You want to create a std::map<std::string, RPCHandler> dispatch so you can do (given a std::vector<RPC> args):
dispatch[command](args);
This map can be constructed as follows:
void test0();
void test2(int, std::string);
std::map<std::string, RPCHandler> dispatch = {
{ "test0", make_invoker(test0) },
{ "test2", make_invoker(test2) },
};
where make_invoker returns a lambda of the correct shape.
The body of this lambda passes the function pointer, argument vector, and a std::index_sequence to invoke_rpc:
template<class... Arg>
RPCHandler make_invoker(void (*f)(Arg...)) {
return [f](std::vector<RPC> args) {
invoke_rpc(f, args, std::index_sequence_for <Arg...>{});
};
}
Finally, invoke_rpc uses std::get on each argument in turn to convert it into the expected type. It does this by expanding the two given template parameter packs in parallel. Intuitively, this expands to f(std::get<Arg0>(args.at(0), std::get<Arg1>(args.at(1)) with as many arguments to f as it expects (since the index sequence has the same length Args...).
template<class... Arg, std::size_t... I>
void invoke_rpc(void (*f)(Arg...), std::vector<RPC> args, std::index_sequence<I...>) {
f(std::get<Arg>(args.at(I))...);
}
If the vector is too short you get a std::out_of_range error, if there is an argument mismatch you get a std::bad_variant_access. You can improve error handling by checking the size of args before calling f, and using std::holds_alternative to see if all passed values match their proscribed type.
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
I want to use a single function to interact with multiple private member variables. I've come up with:
class Some_Vectors {
public:
int is_value_in_vector(string vector_name, int value);
void append_value_to_vector(string vector_name, int value)
private:
vector<int> A;
vector<int> B;
};
// returns value's index if it's there, -1 if not
int Some_Vectors::is_value_in_vector(string vector_name, int value) {
vector<int> *selected_vector;
if (vector_name == "A") {selected_vector = &A;}
else if (vector_name == "B") {selected_vector = &B;}
for (int i = 0; i < selected_vector->size(); i++){
if (selected_vector[0][i] == value){
return i;
}
}
return -1;
}
It works, but I feels unsafe/brittle to compare strings like that. Is there a way to specifically reference a private variable in the function call?
Edited to be a (hopefully) less subjective ask. I ended up using RichardCritten's suggestion of multiple public functions that call a single private function.
You can use the unordered_map to achieve your requirements as below.
Declare the unordered_map as below.
unordered_map<string, vector<int>> umap;
Insert the values to map by using [] operator.
umap["A"] = {10,20};
umap["B"] = {30,40};
Search the key value in the unordered_map as below using find function.
string vector_name = "A";
vector_name = "A";
auto it = umap.find(vector_name);
if (it == umap.end())
return -1;
Once you find the key,value pair in the map search the particular int in the vector as below.
std::vector<int>::iterator iter = std::find(it->second.begin(), it->second.end(), 20);
if iter is not pointing the vector end then return the exact position of the int in the vector as below.
if ( iter != it->second.end())
return std::distance(it->second.begin(),iter);
else
return -1;
Your complete sample code may look like below.
int main()
{
unordered_map<string, vector<int>> umap;
// inserting values by using [] operator
umap["A"] = {10,20};
umap["B"] = {30,40};
string vector_name = "A";
vector_name = "A";
auto it = umap.find(vector_name);
if (it == umap.end())
return -1;
std::vector<int>::iterator iter = std::find(it->second.begin(), it->second.end(), 20);
if ( iter != it->second.end())
return std::distance(it->second.begin(),iter);
else
return -1;
}
I have to disagree with the other answers that suggest maps or any kind of solutions involving strings.
Using strings to identify things in code is very fragile. Some major disadvantages are: no autocomplete, no compile-time checks. There are situations where you don't have a better alternative (e.g. you don't know the identifiers at compile time), but this is not one of them.
One solution is to give meaningful names to the functions. Since you provided a toy example I will use A and B but in real life they should be meaningful names:
class X
{
public:
auto foo_a(int value) { return foo(A, value); }
auto foo_b(int value) { return foo(B, value); }
private:
int foo(std::vector<int>& v, int value) { return 24; }
std::vector<int> A;
std::vector<int> B;
};
If you want one function with a parameter to select the vector, you should select the vector with an enum. This way you have autocomplete and compile-time safety (you can't pass an invalid selector - like you could with a string - unless you bend backwards):
class Y
{
public:
enum class Selector { A, B };
auto foo(Selector selector, int value) { return foo(getVector(selector), value); }
private:
std::vector<int>& getVector(Selector selector)
{
switch (selector)
{
case Selector::A:
return A;
case Selector::B:
return B;
}
}
int foo(std::vector<int>& v, int value) { return 24; }
std::vector<int> A;
std::vector<int> B;
};
Y y{};
y.foo(Y::Selector::A, 11);
y.foo(Y::Selector::B, 1024);
First of all, if you have access to C++17 or later versions of compilers the most modern and preferable way of optional return would be using std::optional.
Regarding your question, as #Dai mentioned in the comments, the best way would be(IMHO also) to use
std::unordered_map<std::string, std::vector</*type*/>>
as the member variable and you can do as follows. See Live here
#include <vector>
#include <string>
#include <unordered_map>
#include <iostream>
using uMapType = std::unordered_map<std::string, std::vector<int>>;
class MyClass
{
private:
uMapType _vecMap;
public:
explicit MyClass(const uMapType& vecMap): _vecMap(std::move(vecMap)) {}
int getValue(const std::string &vector_name, const int value)const
{
// std::unordered_map::find the key(vector name)
auto getVec = _vecMap.find(vector_name);
if(getVec != _vecMap.cend()) // if iterator not pointing to map's end
{
const std::vector<int> &selected_vector = getVec->second;
for (std::size_t i = 0; i < selected_vector.size(); ++i)
if (selected_vector[i] == value)
return i;
}
return -1;
}
};
int main()
{
MyClass obj(
{
{"A", {1, 2, 3, 4, 5}},
{"B", {1, 2, 3, 4, 5}}
});
std::cout << obj.getValue("A", 3) << std::endl; // output: 2
std::cout << obj.getValue("B", 5) << std::endl; // output: 5
std::cout << obj.getValue("C", 3) << std::endl; // output: -1
std::cout << obj.getValue("A", 0) << std::endl; // output: -1
return 0;
}
The std::optional sample solution will look like this.
#include <optional>
using uMapType = std::unordered_map<std::string, std::vector<int>>;
class MyClass
{
private:
uMapType _vecMap;
public:
explicit MyClass(const uMapType& vecMap): _vecMap(std::move(vecMap)) {}
std::optional<int> getValue(const std::string &vector_name, const int value)const
{
if(auto getVec = _vecMap.find(vector_name); getVec != _vecMap.cend())
{
for (std::size_t i = 0; i < getVec->second.size(); ++i)
if (getVec->second[i] == value)
return i;
}
return std::nullopt;
}
};
I have an enum class as follows:
enum class Age
{
Eleven,
Twelve,
Thirteen
};
Then I have a method called vector<Person> GetPeopleOfAge(Age age). What would be a good design so that a developer can call this and get the people with 11, 12 and 13? I can call it three times which is pretty bad but I did want to mention that I considered it. I can add an All enumeration and do a check in my method but I don't like the idea of polluting the enum with enumerations like All just to make my case work. I know it's a common method of solving this and some may disagree with me but to me it feels hacky and am looking for an alternative. Maybe I should use something other than an enum?
Whether All is captured explicitly in the enum or implicitly by another mechanism, you have to deal with the abstraction. Given that, I find it better to deal with it explicitly.
You can use the established method of using values of enums such that they can be combined using bitwise OR operators.
enum Age : unsigned int
{
Eleven = 000001,
Twelve = 000010,
Thirteen = 000100,
All = 000111
};
Then, you can use
// Get all the people of age 11
auto ret1 = GetPeopleOfAge(Age::Eleven);
// Get people of all ages
auto ret2 = GetPeopleOfAge(Age::All);
// Get all the people aged 11 or 13
auto ret3 = GetPeopleOfAge(Age::Eleven | Age::Thirteen);
The obvious solution is to dump the enum: Age is a continuous concept, that may be quantized, but never fully enumerated (What is your highest supported age? 120? 130? 200? 1000? Your choice will be either insensibly large, or have the potential to exclude real persons!). Also, when you talk about age, you frequently need to select ranges of ages.
Consequently, age should be either an int or a float. And your GetPeopleOfAge() function should be declared as
vector<Person> GetPeopleOfAge(int minAge, int maxAge);
No need to complicate things with an enum.
One option is to make the filter parameter optional:
vector<Person> GetPeopleOfAge(std::optional<Age> age = {})
Then, inside the function, use if (age) to check whether age-based filtering should be done or not.
The function should probably be renamed, though, because it does not always give people of a certain age; sometimes, it gives all people.
You can make GetPeopleOfAge variadic (initializer_list might work as well) and give it a better name:
template <typename... Ts>
vector<Person> GetPeopleOfAnyAge(Ts... ages);
// Return a vector of all people having any of `ages...`.
Usage:
const auto people = GetPeopleOfAnyAge(
Age::Eleven, Age::Twelve, Age::Thirteen);
If you commonly get people of all ages, you can create a wrapper:
const auto getAllPeople = []
{
return GetPeopleOfAnyAge(
Age::Eleven, Age::Twelve, Age::Thirteen);
};
I'd use a predicate to filter out the returned list. Then the caller can use whatever criterion they want for the subset. (Combining ideas from François and Vittorio.)
Example:
#include <algorithm>
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <string>
#include <vector>
using std::cout;
using std::endl;
using std::function;
using std::initializer_list;
using std::ostream;
using std::string;
using std::vector;
enum class Gender { unknown, male, female };
class Person {
string name;
int age;
Gender gender;
public:
Person(string n, int a, Gender g) : name{move(n)}, age{a}, gender{g} { }
string Name() const { return name; }
int Age() const { return age; }
Gender Gender() const { return gender; }
};
ostream& operator<<(ostream& o, Person const& person) {
o << person.Name() << "(" << person.Age() << ", ";
switch (person.Gender()) {
case Gender::unknown: o << "?"; break;
case Gender::male: o << "boy"; break;
case Gender::female: o << "girl"; break;
}
o << ")";
return o;
}
class People {
vector<Person> people;
public:
People(initializer_list<Person> l) : people{l} { }
vector<Person> GetPeople(function<bool(Person const&)> predicate);
};
vector<Person> People::GetPeople(function<bool(Person const&)> predicate) {
vector<Person> result;
for (auto const& person : people) {
if (predicate(person)) {
result.push_back(person);
}
}
return result;
}
ostream& operator<<(ostream& o, vector<Person> const& vector_of_person) {
char const* sep = "";
for (auto const& person : vector_of_person) {
o << sep << person;
sep = ", ";
}
return o;
}
int main() {
auto const b = Gender::male;
auto const g = Gender::female;
People people = {{"Anu", 13, g}, {"Bob", 11, b}, {"Cat", 12, g}, {"Dex", 11, b}, {"Eli", 12, b}};
auto ageUnder13 = [](Person const& p) { return p.Age() < 13; };
cout << people.GetPeople(ageUnder13) << endl;
auto everyone = [](Person const& p) { return true; };
cout << people.GetPeople(everyone) << endl;
auto boys = [](Person const& p) { return p.Gender() == Gender::male; };
cout << people.GetPeople(boys) << endl;
return EXIT_SUCCESS;
}
While R Sahu just has been quicker than me working on the same idea, coming back to your comment:
It looks like every solution I look at is not quite perfect. This one, the enum is not type safe [...]
If you want to retain the scoped enum for whatever reason, you can define the necessary operators yourself, see below (a little bit of work, admitted – well, what about "perfect"?). About the All value: well, no-one said you need to include it, it was R Sahu's preference (while mine is opposite...) – in the end, this is rather a matter of use case, though...
enum class E
{
E0 = 1 << 0,
E1 = 1 << 1,
E2 = 1 << 2,
E3 = 1 << 3,
};
E operator|(E x, E y)
{
return static_cast<E>
(
static_cast<std::underlying_type<E>::type>(x)
|
static_cast<std::underlying_type<E>::type>(y)
);
}
E operator&(E x, E y)
{
return static_cast<E>
(
static_cast<std::underlying_type<E>::type>(x)
&
static_cast<std::underlying_type<E>::type>(y)
);
}
bool operator==(E x, std::underlying_type<E>::type y)
{
return static_cast<std::underlying_type<E>::type>(x) == y;
}
bool operator!=(E x, std::underlying_type<E>::type y)
{
return !(x == y);
}
bool operator==(std::underlying_type<E>::type y, E x)
{
return x == y;
}
bool operator!=(std::underlying_type<E>::type y, E x)
{
return x != y;
}
void f(E e)
{
E person = E::E1;
if((person & e) != 0)
{
// add to list...
}
}
int main(int argc, char *argv[])
{
f(E::E0 | E::E1);
return 0;
}
Why not be insane?
enum class subset_type {
include, all
};
struct include_all_t { constexpr include_all_t() {} };
constexpr include_all_t include_all {};
template<class E>
struct subset {
subset_type type = subset_type::include;
std::variant<
std::array<E, 0>, std::array<E, 1>, std::array<E, 2>, std::array<E, 3>, std::array<E, 4>,
std::vector<E>
> data = std::array<E,0>{};
// check if e is in this subset:
bool operator()( E e ) const {
// Everything is in the all subset:
if(type==subset_type::all)
return true;
// just do a linear search. *this is iterable:
for (E x : *this)
if (x==e) return true;
return false;
}
// ctor, from one, subset of one:
subset(E e):
type(subset_type::include),
data(std::array<E, 1>{{e}})
{}
// ctor from nothing, nothing:
subset() = default;
// ctor from {list}, those elements:
subset(std::initializer_list<E> il):
type(subset_type::include)
{
populate(il);
}
// ctor from include_all:
subset(include_all_t):type(subset_type::all) {}
// these 3 methods are only valid to call if we are not all:
E const* begin() const {
return std::visit( [](auto&& x){ return x.data(); }, data );
}
std::size_t size() const {
return std::visit( [](auto&& x){ return x.size(); }, data );
}
E const* end() const {
return begin()+size();
}
// this method is valid if all or not:
bool empty() const {
return type!=subset_type::all && size()==0;
}
// populate this subset with the contents of srcs, as iterables:
template<class...Src>
void populate(Src const&...srcs) {
std::size_t count = (std::size_t(0) + ... + srcs.size());
// used to move runtime count to compiletime N:
auto helper = [&](auto N)->subset& {
std::array<E, N> v;
E* ptr = v.data();
auto add_src = [ptr](auto& src){
for (E x:src)
*ptr++ = x;
};
(add_src(srcs),...);
this->data = v;
};
// handle fixed size array cases:
switch(count) {
case 0: return helper(std::integral_constant<std::size_t, 0>{});
case 1: return helper(std::integral_constant<std::size_t, 1>{});
case 2: return helper(std::integral_constant<std::size_t, 2>{});
case 3: return helper(std::integral_constant<std::size_t, 3>{});
case 4: return helper(std::integral_constant<std::size_t, 4>{});
default: break;
};
// otherwise use a vector:
std::vector<E> vec;
vec.reserve(count);
auto vec_helper = [&](auto&& c){
for (E x:c) vec.push_back(c);
};
(vec_helper(srcs), ...);
data = std::move(vec);
}
// because what is a set without a union operator?
friend subset& operator|=( subset& lhs, subset const& rhs ) {
if (lhs.type==subset_type::all) return lhs;
if (rhs.type==subset_type::all) {
lhs.type = subset_type::all
return lhs;
}
populate( lhs, rhs );
return lhs;
}
friend subset operator|( subset lhs, subset const& rhs ) {
lhs |= rhs;
return std::move(lhs);
}
};
C++17 and probably typos.
std::vector<Person> GetPeopleOfAge(subset<Age> age)
you can call it with Age::Eleven, or with include_all, or with {} for none, or with {Age::Eleven, Age::Twelve} for two.
It uses a small buffer optimization to handle up to 4 elements.
If not in all mode, you can iterate over the elements in the subset using range-based for loops.
Adding support for operator&, subset_type::none, subset_type::exclude, and operator~ left as an exercise.
You could do a combination of things to achieve this borrowing bits and pieces from other's comments and answers.
Create a variadic template using std::tuple
Use Bit logic on your enum(s) - typical what is seen in many API functions to use multiple settings such as: glClearColor( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); These are typically called piping using bitfields.
Create an instance of an object that is a result of a single vector of each where that vector is accumulated so to speak using the above mechanisms.
Something of this nature may work for you:
enum class Gender {
male,
female,
both,
other
};
class age_constraints {
public:
const static unsigned min_age { 1 };
const static unsigned max_age { 130 };
protected:
age_constraints() = default;
}; typedef age_constraints age_range;
class attributes {
public:
std::string firstName;
std::string middleNameOrInitial;
std::string lastName;
unsigned age;
Gender gender;
attributes() = default;
attributes( const std::string& first, const std::string& middle, const std::string& last,
const unsigned& ageIn, const Gender& gend ) :
firstName( first ),
middleNameOrInitial( middle ),
lastName( last ),
age( ageIn ),
gender( gend )
{}
};
class Person {
private:
attributes attribs;
public:
Person() = default;
explicit Person( const attributes& attribsIn ) : attribs( attribsIn ) {}
Person( const std::string& firstName, const std::string& middle, const std::string& lastName,
const unsigned& age, const Gender& gender ) :
attribs( firstName, middle, lastName, age, gender ) {}
// other methods
};
class Database {
public:
const static age_range range;
private:
std::vector<std::shared_ptr<Person>> peopleOnFile;
public:
Database() = default;
void addPerson( const Person&& person ) {
peopleOnFile.emplace_back( new Person( person ) );
}
template<bool all = false>
std::vector<Person> getPeopleByAges( unsigned minAge, unsigned maxAge, unsigned ages... ) {
std::tuple<ages> agesToGet( ages... );
std::vector<Person> peopleToGet;
// compare tuple ages with the ages in Person::attributes - don't forget that you can use the age_constraints for assistance
// populate peopleToGet with appropriate age range
return peopleToGet;
}
template<bool all = true>
std::vector<Person> getPeopleByAges() {
return peopleOnFile;
}
};
In the database example above: what I've done in pseudo code is shown that the heavy or bulk work of the code is done in the version of the function that searches within a range, where the overloaded method to find all doesn't take any parameters and just returns the full vector.
I have taken some ideas from all of the answers I've seen and here's the best solution I could come up with.
class People
{
public:
GetPeopleOfAllAges()
{
GetPeopleOfAge();
}
private:
GetPeopleOfAge(Age age = NULL)
{
if (age == NULL ||
*age == Person.age)
{
// Add Person to list.
}
}
}
In this scenario, NULL means get me everything which is not ideal but at least it is hidden away from the UI layer and I am not violating the age property on Person which cannot be All Would love to hear some thoughts on the approach.
Here is my code:
#include <functional>
#include <iostream>
#include<vector>
using namespace std;
// vector iterator
template <class T> class vit
{
private:
//vector<T>::iterator it;
vector<T> m_v;
function<bool (T, T)> m_fptr;
int len, pos;
public:
vit(vector<T> &v) { this->m_v = v; len = v.size(); pos = 0;};
// it= v.begin(); };
bool next(T &i) {
//if(it == m_v.end()) return false;
if(pos==len) return false;
//i = *it;
i = m_v[pos];
//if(idle) { idle = false ; return true; }
//it++;
pos++;
return true;};
//bool idle = true;
void set_same(function<bool (T,T)> fptr) { m_fptr = fptr ;};
//void set_same(function<bool(int, int)> fun) { return ; }
bool grp_begin() {
return pos == 0 || ! m_fptr(m_v[pos], m_v[pos-1]); };
bool grp_end() {
return pos == len || ! m_fptr(m_v[pos], m_v[pos+1]); };
};
bool is_same(int a, int b) { return a == b; }
main()
{
vector<int> v ={ 1, 1, 2, 2, 2, 3, 1, 1, 1 };
int total;
for(auto it = v.begin(); it != v.end(); it++) {
if(it == v.begin() || *it != *(it-1)) {
total = 0;
}
total += *it;
if(it+1 == v.end() || *it != *(it+1)) {
cout << total << endl;
}
}
cout << "let's gry a group" <<endl;
vit<int> g(v);
int i;
while(g.next(i)) { cout << i << endl; }
cout << "now let's get really fancy" << endl;
vit<int> a_vit(v);
//auto is_same = [](int a, int b) { return a == b; };
a_vit.set_same(is_same);
//int total;
while(a_vit.next(i)) {
if(a_vit.grp_begin()) total = 0;
total += i;
if(a_vit.grp_end()) cout << total << endl ;
}
}
When I compile it with g++ -std=c++11 iter.cc -o iter, I get the result:
iter.cc: In function 'int main()':
iter.cc:63:17: error: reference to 'is_same' is ambiguous
a_vit.set_same(is_same);
^
iter.cc:37:6: note: candidates are: bool is_same(int, int)
bool is_same(int a, int b) { return a == b; }
^
In file included from /usr/include/c++/5.3.0/bits/move.h:57:0,
from /usr/include/c++/5.3.0/bits/stl_pair.h:59,
from /usr/include/c++/5.3.0/utility:70,
from /usr/include/c++/5.3.0/tuple:38,
from /usr/include/c++/5.3.0/functional:55,
from iter.cc:1:
/usr/include/c++/5.3.0/type_traits:958:12: note: template<class, class> struct std::is_same
struct is_same;
^
By way of explanation, I have created a class called 'vit'. It does two things: iterate over a vector, and determine if a new group has been reached.
The class function 'set_same' is supposed to store a function provided by the calling class to determine if two adjacent elements of a vector are in the same group. However, I can't seem to store the function in the class for future use by grp_begin() and grp_end() on account of the ostensible ambiguity of is_same.
What gives?
There is an is_same function defined by you and there is a struct is_same defined by the C++ Standard Library. Since you are using namespace std, your compiler doesn't know which is_same you meant to use.
It's what the error says: it's not clear whether you mean your is_same (in the global namespace) or the class template is_same (in namespace std).
You may disambiguate as follows:
::is_same
… with the leading :: meaning "in the global namespace".
Though you should consider putting your code in a namespace of its own.
Thanks guys. This is my first time touching C++ after more than a decade. I have cleaned up the code, and used a lambda to bring the "is_same" function closer to where it is called.
Did you spot the bug in my code? 'pos' was off-by-one when calling grp_begin() and grp_end(). Here is the revised code:
#include <functional>
#include <iostream>
#include <vector>
// vector iterator
template <class T> class vit
{
private:
std::vector<T> m_v;
std::function<bool (T, T)> m_fptr;
int len, pos;
public:
vit(std::vector<T> &v) { m_v = v; len = v.size(); pos = -1;};
bool next(T &val) {
pos++;
if(pos==len) return false;
val = m_v[pos];
return true;};
void set_same(std::function<bool (T,T)> fptr) { m_fptr = fptr ;};
bool grp_begin() {
return pos == 0 || ! m_fptr(m_v[pos], m_v[pos-1]); };
bool grp_end() {
return pos+1 == len || ! m_fptr(m_v[pos], m_v[pos+1]); };
};
main()
{
std::vector<int> v ={ 1, 1, 2, 2, 2, 3, 1, 1, 1 };
vit<int> a_vit(v);
std::function<bool (int, int)> is_same = [](int a, int b) { return a == b; };
a_vit.set_same(is_same);
int i, total;
while(a_vit.next(i)) {
if(a_vit.grp_begin()) total = 0;
total += i;
if(a_vit.grp_end()) std::cout << total << std::endl ;
}
}
My class definition isn't bullet-proof and could be better: if the user forgets to 'set-same', for example, they'll be referring a random memory address as a function.
Nevertheless, I'm pretty chuffed with my solution so far. The class caller is relieved of all the bookkeeping relating iterating over the vector, and working out if a group boundary has been crossed.
The calling code looks very compact and intuitive to me.I can see C++ being my go to language.