TransformerClangTidyCheck drops required parentheses around bound `expr()` - c++

I'd like to rename a function on one of the classes:
class Container {
public:
int count() const; // old
int size() const; // new
};
So I've written a TransformerClangTidyCheck along the lines of the tutorial (https://clang.llvm.org/docs/ClangTransformerTutorial.html#example-rewriting-method-calls):
ReproParenIssueCheck::ReproParenIssueCheck(llvm::StringRef Name, ClangTidyContext *Context)
: utils::TransformerClangTidyCheck(Name, Context)
{
const std::string o = "object";
auto hasTypeIgnoringPointer = [](auto type) { return anyOf(hasType(type), hasType(pointsTo(type))); };
auto derivedFromClass = [&](StringRef Class) {
auto exprOfDeclaredType = [&](auto decl) {
return hasTypeIgnoringPointer(hasUnqualifiedDesugaredType(recordType(hasDeclaration(decl))));
};
return exprOfDeclaredType(cxxRecordDecl(isSameOrDerivedFrom(hasName(Class))));
};
auto renameMethod = [&] (StringRef Class, StringRef from, StringRef to) {
return makeRule(cxxMemberCallExpr(on(expr(derivedFromClass(Class)).bind(o)),
callee(cxxMethodDecl(hasName(from), parameterCountIs(0)))),
changeTo(cat(access(o, cat(to)), "()")),
cat("use '", to, "' instead of '", from, "'"));
};
setRule(renameMethod("Container", "count", "size"));
}
This works:
int test_ok(const Container *p) {
- return p->count();
+ return p->size();
}
except that changeTo() is losing the required ParenExpr in the expr() bound to o:
int test_fail(const void *p) {
- return ((const Container*)p)->count();
+ return ((const Container*)p)->size(); // expected
+ return (const Container*)p->size(); // actual
}
Bug in changeTo() or am I missing something? It looks like any ParenExpr is dropped:
int test_fail2(const Container &c) {
- return (c).count();
+ return (c).size(); // expected
+ return c.size(); // actual
}

It turns out that the on() matcher drops the parentheses:
AST_MATCHER_P(CXXMemberCallExpr, on, internal::Matcher<Expr>,
InnerMatcher) {
const Expr *ExprNode = Node.getImplicitObjectArgument()
->IgnoreParenImpCasts(); // <-- HERE
return (ExprNode != nullptr &&
InnerMatcher.matches(*ExprNode, Finder, Builder));
}
If I create a new matcher that only ignores implicit casts:
AST_MATCHER_P(CXXMemberCallExpr, onIgnoringImpCasts, Matcher<Expr>,
InnerMatcher) {
const Expr *ExprNode = Node.getImplicitObjectArgument()
->IgnoreImpCasts();
return (ExprNode != nullptr &&
InnerMatcher.matches(*ExprNode, Finder, Builder));
and use that instead of on(), both test cases pass.

Related

Find any element in a map by matching a sub-element of the key

I have a std::map whose key is another class. Like the example below:
class KeyClass
{
public:
int a;
int b;
};
main(){
//some code
std::map<KeyClass, SomeOtherClass> mapVariable;
KeyClass k1(); k1.a = 1; k1.b = 1;
KeyClass k2(); k2.a = 2; k2.b = 2;
mapVariable[k1] = SomeOtherClass();
mapVariable[k2] = SomeOtherClass();
//I am trying something like see if any element is there whose key.a = 1 (or may be key.a = 6 --> will fail here)?
}
I tried looping across mapVariable and checking iter->first.a == 1, but is there anyway I can use the map::count() function to get that?
I am using this in a CLI C++ code, so it seem that I am unable to use a lambda function.
You can use std::find_if() to search the map using a custom predicate, eg:
#include <algorithm>
using myMapType = std::map<KeyClass, SomeOtherClass>;
myMapType mapVariable;
...
auto iter = std::find_if(
mapVariable.begin(), mapVariable.end(),
[](const myMapType::value_type &entry){ return entry.first.a == 1; }
);
if (iter != mapVariable.end()) {
...
}
If using a lambda is not an option, you can use a functor instead:
#include <algorithm>
typedef std::map<KeyClass, SomeOtherClass> myMapType;
struct findKey
{
bool operator()(const myMapType::value_type &entry) const { return entry.first.a == 1; }
};
myMapType mapVariable;
...
myMapType::iterator iter = std::find_if(mapVariable.begin(), mapVariable.end(), findKey());
if (iter != mapVariable.end()) {
...
}
If you don't want to use std::find_if(), you can use a manual loop:
myMapType::iterator iter = mapVariable.begin();
while (iter != mapVariable.end()) {
if (iter->first.a == 1) break;
++iter;
}
if (iter != mapVariable.end()) {
...
}
You can use std::find_if with a lambda:
auto it = std::find_if(mapVariable.begin(), mapVariable.end(), [](const auto& pair) {
return pair.first.a == 1; // first is the key, second is the value
});
Or without using a lambda:
struct Functor {
bool operator()(const decltype(mapVariable)::value_type& pair) const {
return pair.first.a == 1;
}
};
auto it = std::find_if(mapVariable.begin(), mapVariable.end(), Functor{});
Then check if you got a match:
if(it != mapVariable.end()) {
// match found
}
To be able to use the size_type std::map::count( const K& x ) const overload, the comparison function used in the map would need to be transparent.
Example:
struct Compare {
using is_transparent = int;
bool operator()(int x, const KeyClass& b) const {
return x < b.a;
}
bool operator()(const KeyClass& a, int x) const {
return a.a < x;
}
bool operator()(const KeyClass& a, const KeyClass& b) const {
return a.a < b.a;
}
};
//...
std::map<KeyClass, SomeOtherClass, Compare> mapVariable;
//...
if(mapVariable.count(1)) {
// match found
}

Fast computation of logic gates

I created the following code to compute the result of a logic gate (AND, OR, NOT). The function will be used in a circuit simulation where the circuits are read from a netlist file. A circuit could consist of up to 50000 logic gates.
Based on the fact that this function is often called during the simulation I would like to know if it could be implemented in another way so the generated machine code would be more efficient?
A logic gate could have more than two inputs (except NOT with only one input) but most logic gates have only two. So I thought about testing for two inputs and then write something like this: return input->predecessors[0]->result && return input->predecessors[1]->result; and return input->predecessors[0]->result || return input->predecessors[1]->result; But this would probably introduce new branches. The number of the inputs could be stored in the Node directly to prevent the call of the size() method.
#include <vector>
enum class NodeType { NOT, AND, OR };
struct Node {
NodeType type;
bool result;
std::vector<Node *> predecessors;
};
bool evaluate(Node *input) {
switch (input->type) {
case NodeType::NOT: {
return !input->predecessors[0]->result;
}
case NodeType::AND: {
bool result = true;
for (const auto &node : input->predecessors) {
result = result && node->result;
}
return result;
}
case NodeType::OR: {
bool result = false;
for (const auto &node : input->predecessors) {
result = result || node->result;
}
return result;
}
};
};
I'd be tempted to get the first input and merge its state into the switch(); like:
bool result = input->predecessors[0];
switch((input->type << 1) | result) {
case (NodeType::NOT << 1) | false:
return true;
case (NodeType::NOT << 1) | true:
return false;
case (NodeType::AND << 1) | false:
return false;
case (NodeType::OR << 1) | true:
return true;
case (NodeType::AND << 1) | true: {
for (const auto &node : input->predecessors) { // Note: Can skip 1st iteration
result = result && node->result;
if(result == false) {
return false;
}
}
return true;
}
case (NodeType::OR << 1) | false:
for (const auto &node : input->predecessors) { // Note: Can skip 1st iteration
result = result || node->result;
if(result == true) {
return true;
}
}
return false;
}
The hope being that the compiler will be able to convert this into a jump table (e.g. a single "jmp [table+rax*8]" instruction replacing all the switch() and half the rest of the code).
WARNING: For this to work you have to make sure that input->predecessors[0] uses 1 for "true" (and that no other value is used for true). If that is a potential concern; you can use bool result = !!input->predecessors[0];
It really looks like what you are doing is an interface.
struct Node {
std::vector<Node *> predecessors;
virtual bool evaluate() const;
};
struct NodeNot : Node {
bool evaluate() const {
return !input->predecessors[0]->result;
}
};
struct NodeAnd : Node {
bool evaluate() const {
for (const auto &node : input->predecessors) {
if(!node->result) {
// there is no need to accumulate the result
// fail fast
return false;
}
}
return true;
}
};
struct NodeOr : Node {
bool evaluate() const {
for (const auto &node : input->predecessors) {
if (node->result) {
return true;
}
}
return false;
}
};
That way you eliminate the need for the switch completely and achieve same result with just a single virtual call. It may be faster or slower method then the switch, it really depends on many factors and how good you are caching the result in Node::result member. Profile your code to be sure what works best.
I was looking at using std::variant. Still a bit hacky, because I'm using void pointers... any help on cleaning this up would be nice
#include <tuple>
#include <variant>
#include <stdexcept>
#include <assert.h>
using vcpc = void const* const;
struct NOT { vcpc ptr; };
struct OR { vcpc ptr1; vcpc ptr2; };
struct AND { vcpc ptr1; vcpc ptr2; };
using Node = std::variant<NOT, OR, AND, bool>;
// from https://en.cppreference.com/w/cpp/utility/variant/visit
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;
using Ncpc = Node const* const;
constexpr bool evaluate(Ncpc input) {
return std::visit(overloaded{
[](NOT const& arg) { return !evaluate((Ncpc)arg.ptr); },
[](OR const& arg) { return evaluate((Ncpc)arg.ptr1) || evaluate((Ncpc)arg.ptr2); },
[](AND const& arg) { return evaluate((Ncpc)arg.ptr1) && evaluate((Ncpc)arg.ptr2); },
[](bool arg) { return arg; },
}, *input);
}
int main() {
Node const isTrue{ true };
Node const invTrue{ NOT{&isTrue} };
assert(evaluate(&invTrue) == false);
Node const andTrueFalse{ AND{&isTrue, &invTrue} };
assert(evaluate(&andTrueFalse) == false);
Node const orTrueFalse{ OR{&isTrue, &andTrueFalse} };
assert(evaluate(&orTrueFalse) == true);
}

File reading error in Reader class implementation

I'm trying to implement a Reader class for my own hobby programming language which I'm working on. The job of the reader would be very simple, to read the source file, and remove comments.
Here's the definition of the Reader class:
// reader.hh
// Contains Reader Class specifications
#ifndef PHI_SRC_FRONTEND_READER_HH
#define PHI_SRC_FRONTEND_READER_HH
#include "errhandler.hh"
class Reader
{
public:
Reader() = default;
auto val() const -> String const & { return val_; }
void read(String const &filename);
explicit operator bool() const { return success; }
auto operator!() const -> bool { return !success; }
friend auto operator==(Reader const &lhs, Reader const &rhs) -> bool
{
return lhs.val_ == rhs.val_;
}
friend auto operator!=(Reader const &lhs, Reader const &rhs) -> bool
{
return lhs.val_ != rhs.val_;
}
friend auto operator<<(std::ostream &stream, Reader const &read) -> std::ostream &
{
return stream << read.val_;
}
private:
String val_;
bool success;
};
#endif
Previously, I used a very simple placeholder for the Reader's read function. It basically copied everything inside the file using an istreambuf_iterator
void Reader::read(String const &filename)
{
val_.clear();
success = true; // success flag, true by default
auto file = std::ifstream{filename};
if(!file)
{
log_error(Error{Error::Type::ReadError, "Could not open file"});
success = false;
}
val_.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
// Read entire file into val_
}
This worked fine, it passed the unit tests, and I manually checked the output as well, which was also fine.
But this was just a placeholder, the actual Reader needed to remove comments. Which made me implement this:
// reader.cc
// Contains Reader Class Implementation
// Work In Progress, placeholders being used for now
#include <fstream>
#include <sstream>
#include "reader.hh"
void Reader::read(String const &filename)
{
val_.clear();
success = true; // success flag, true by default
auto inStringLiteral = false;
// Variable to determine if the reader is currently reading a string literal
// (enclosed in double quotes)
// In order to not mistake '//' inside literals as comments"
auto file = std::ifstream{filename};
if(!file)
{
log_error(Error{Error::Type::ReadError, "Cannot open file: " + filename});
success = false;
return;
}
for (unsigned char c; file >> c; )
{
// ASCII characters only use 7 bits, which is up to 127
// So value of an ascii char must be lesser than 128
if (c < 128)
{
if(c == '"')
{
inStringLiteral = !inStringLiteral; // flip the value of the boolean
}
if(!inStringLiteral && c == '/')
{
// If we're not inside a string literal enclosed in quotes, and find a backslash
// Peek at the next character to check if it is a backslash
// In case two consecutive backslashes are found, treat it as a comment and
// ignore everything until the end of line
if(file >> c)
{
if(c == '/')
{
// keep reading until a newline is found
while(file >> c && c != '\n')
{
}
}
else
{
c = '/';
file.unget();
}
}
else
{
c = '/';
}
}
val_ += c;
}
else
{
log_error(Error{Error::Type::ReadError, "Unrecognized character(s) found in file: " + filename});
success = false;
return;
}
}
}
However, this weirdly causes the Unit Tests to fail.. I've compared the outputs of both versions of the read function, both have (apparently) the exact same output. Note I haven't actually checked for equality of the strings, but they do look the same. I've tried to find the reason for the error a lot but have failed....
Here's the Unit Test I'm using for the Reader (using GoogleTest):
#include <gtest/gtest.h>
#include "frontend/reader.hh"
TEST(ReaderTest, BaseTestCase)
{
auto TestReader = Reader{};
auto const ExpectedOutput = String{
R"delim(Int test = 0;
String test2 = "abcdefgh";
Float test3 = 0.9876;
)delim"};
TestReader.read("TestFiles/ReaderTest_BaseTestCase.phi");
ASSERT_FALSE(!TestReader);
ASSERT_EQ(TestReader.val(), ExpectedOutput);
// If Reader Base Test Case fails, no need to continue next tests
}
TEST(ReaderTest, Should_Fail_When_FileDoesNotExist)
{
auto TestReader = Reader{};
TestReader.read("Non_existent_test_file.txt");
EXPECT_TRUE(!TestReader);
}
As I mentioned before, it worked quite fine for the first placeholder version, but the actual read function doesn't seem to pass the tests.... The weird thing is, the sample file doesn't even have any comments, here's the sample file the Reader reads:
Int test = 0;
String test2 = "abcdefgh";
Float test3 = 0.9876;
(Yes, that's literally it. Also as I mentioned before, the language the Reader is reading is not C++, but rather a homemade language I'm working on reading, but that's probably irrelevant for this question).
Oh and in case you need to compile this, you'd need to implementation and definition of errhandler (errhandler.hh and errhandler.cc), I'll put them here as well:
Declaration (errhandler.hh):
// errhandler.hh
// Contains Phi Error Handling specifications
// Mostly complete, minor changes still might be made though
#ifndef PHI_SRC_FRONTEND_ERRHANDLER_HH
#define PHI_SRC_FRONTEND_ERRHANDLER_HH
#include <iostream>
#include "utils.hh"
class Error
{
public:
enum class Type : unsigned char
{
ReadError, LexError, ParseError, SemanticError, InterpretError
};
Error() = delete;
Error(Type type__, String const &val__) : type_(type__), val_(val__) {}
auto type() const -> Type { return type_; }
auto val() const -> String { return val_; }
friend auto operator<<(std::ostream &stream, Error const &error) -> std::ostream&
{
return stream << error.val();
}
private:
Type type_;
String val_;
};
class ErrorLog
{
public:
using iterator = Vector<Error>::iterator;
using const_iterator = Vector<Error>::const_iterator;
using reverse_iterator = Vector<Error>::reverse_iterator;
using const_reverse_iterator = Vector<Error>::const_reverse_iterator;
void push(Error const &error) { errors.push_back(error); }
void pop() { errors.pop_back(); }
auto size() const -> Size { return errors.size(); }
auto operator[](Size index) -> Error& { return errors[index]; }
auto operator[](Size index) const -> Error const& { return errors[index]; }
auto begin() -> iterator { return errors.begin(); }
auto end() -> iterator { return errors.end(); }
auto cbegin() const -> const_iterator { return errors.cbegin(); }
auto cend() const -> const_iterator { return errors.cend(); }
auto rbegin() -> reverse_iterator { return errors.rbegin(); }
auto rend() -> reverse_iterator { return errors.rend(); }
auto crbegin() -> const_reverse_iterator { return errors.crbegin(); }
auto crend() -> const_reverse_iterator { return errors.crend(); }
friend auto operator<<(std::ostream &stream, ErrorLog const &error_log) -> std::ostream&
{
for (Size i = 0; i < error_log.size(); i++)
stream << error_log[i];
return stream;
}
private:
Vector<Error> errors;
};
void log_error(Error const &error);
void show_errors(std::ostream& stream);
extern ErrorLog errlog;
// The global error log to be used by every part of the Phi system
// To be declared in main()
#endif
Definition (errhandler.cc):
// errhandler.cc
// Contains Phi Error Handling implementation
// Work In Progress, placeholders are temporarily being used
#include "errhandler.hh"
void log_error(Error const& error)
{
errlog.push(error);
}
void show_errors(std::ostream& stream)
{
stream << errlog;
}
And you also, finally, need the utils header, containing the utilities
// utils.hh
// Contains globally used utility functions, templates, using declarations, etc.
#ifndef PHI_SRC_UTILS_HH
#define PHI_SRC_UTILS_HH
#include <string>
#include <vector>
#include <limits>
#define UNUSED(x) (void)x
template <typename T>
using Vector = std::vector<T>;
using String = std::string;
using Size = std::size_t;
class bad_narrow_cast : public std::bad_cast
{
public:
bad_narrow_cast(const char* message) : what_(message)
{
}
char const *what() { return what_; }
private:
char const *what_;
};
template <typename Target, typename Base> static inline
typename std::enable_if<std::numeric_limits<Target>::is_specialized,
Target>::type narrow_cast(Base const &base)
{
if(base > static_cast<Base>(std::numeric_limits<Target>::max()) ||
base < static_cast<Base>(std::numeric_limits<Target>::min()))
{
throw(bad_narrow_cast((String() + "Invalid narrowing conversation from type " +
typeid(Target).name() + " to type " + typeid(Base).name()).c_str()));
}
return static_cast<Target>(base);
}
template <typename Target, typename Base> static inline
typename std::enable_if<!std::numeric_limits<Target>::is_specialized,
Target>::type narrow_cast(Base const &base)
{
Target target = static_cast<Target>(base);
Base narrowed_base = static_cast<Base>(target);
if (base == narrowed_base)
return target;
throw(bad_narrow_cast((String() + "Invalid narrowing conversation from type " +
typeid(Target).name() + " to type " + typeid(Base).name()).c_str()));
}
#endif
This question really halted my progress on the project. Helping me solve it would be really helpful
So, people in the comments really helped me, and suggested me to use >> for reading characters from the stream instead of eof() and get(), as eof() is unreliable.. But even that didn't solve the problem. Until I, through some googling, figured it out myself, I had to use std::noskipws, in order to make the >> operator not skip whitespaces, and then it worked. Thanks for all the help, I really appreciate it

Is it possible to define a variable that can be set only once?

I know of const, that can't be changed after creation. But I was wondering if there is a way to declare a variable that you set only once and after that, can't overwrite.
In my code, I would like to avoid the bool variable by having an nFirst that, once set to nIdx, can't be set to the new value of nIdx.
My code:
int nFirst = 0;
int nIdx = 0;
bool bFound = false;
BOOST_FOREACH(Foo* pFoo, aArray)
{
if (pFoo!= NULL)
{
pFoo->DoSmth();
if (!bFound)
{
nFirst= nIdx;
bFound = true;
}
}
nIdx++;
}
Pretty easy to roll your own.
template<typename T>
class SetOnce
{
public:
SetOnce(T init) : m_Val(init)
{}
SetOnce<T>& operator=(const T& other)
{
std::call_once(m_OnceFlag, [&]()
{
m_Val = other;
});
return *this;
}
const T& get() { return m_Val; }
private:
T m_Val;
std::once_flag m_OnceFlag;
};
Then just use the wrapper class for your variable.
SetOnce<int> nFirst(0);
nFirst= 1;
nFirst= 2;
nFirst= 3;
std::cout << nFirst.get() << std::endl;
Outputs:
1
I would like to avoid the bool variable
You can check nFirst itself, based on the fact that it won't be set a negative number. Such as:
int nFirst = -1;
int nIdx = 0;
BOOST_FOREACH(Foo* pFoo, aArray)
{
if (pFoo != NULL)
{
pFoo->DoSmth();
if (nFirst == -1)
{
nFirst = nIdx;
}
}
nIdx++;
}
Similar to cocarin's, but throws exception instead of silently ignoring assignment:
template <typename T, typename Counter = unsigned char>
class SetOnce {
public:
SetOnce(const T& initval = T(), const Counter& initcount = 1):
val(initval), counter(initcount) {}
SetOnce(const SetOnce&) = default;
SetOnce<T, Counter>& operator=(const T& newval) {
if (counter) {
--counter;
val = newval;
return *this;
}
else throw "Some error";
}
operator const T&() const { return val; } // "getter"
protected:
T val;
Counter counter;
};
Usage:
SetOnce<int> x = 42;
std::cout << x << '\n'; // => 42
x = 4;
// x = 5; // fails
std::cout << x << '\n'; // => 4
Online demo
Your question is about avoiding the bool but also implies the need for const-ness.
To avoid the bool, I'd use a boost::optional like this:
boost::optional<int> nFirst;
// ..
if (!nFirst) nFirst = nIdx;
// and now you can use *nFirst to get its value
Then, you can enforce logical (rather than literal) const-ness like this:
const boost::optional<int> nFirst;
// ..
if (!nFirst) const_cast<boost::optional<int>&>(nFirst) = nIdx;
// you can use *nFirst to get the value, any attempt to change it would cause a compile-time error
Using const_cast is not the safest practice, but in your particular case and as long as you only do it once it'd be OK. It simplifies both your code and your intentions: you do want a const, it's just that you want to defer it's initialisation for a bit.
Now, as songyuanyao suggested, you could directly use an int instead of a boost::optional, but the latter makes your intention explicit so I think it's better this way. In the end of day this is C++ while songyuanyao's solution is really a C-style one.
This is set once template. You can use this class as assurance that the value will be set and saved only once. Every next try will be canceled.
#include <iostream>
using namespace std;
template <class T>
class SetOnce;
template<class T>
std::ostream& operator<<( ostream& os, const SetOnce<T>& Obj );
template <class T>
class SetOnce
{
public:
SetOnce() {set = false; }
~SetOnce() {}
void SetValue(T newValue) { value = !set ? newValue : value; set = true; }
private:
T value;
bool set;
friend std::ostream& operator<< <>( ostream& os, const SetOnce& Obj );
public:
SetOnce<T>& operator=( const T& newValue )
{
this->SetValue(newValue);
return *this;
}
};
template<class T>
std::ostream& operator<<( ostream& os, const SetOnce<T>& Obj )
{
os << Obj.value;
return os;
}
Use case:
int main()
{
SetOnce<bool> bvar;
SetOnce<int> ivar;
SetOnce<std::string> strvar;
std::cout<<"initial values: \n"<<bvar<<" "
<<ivar<<" "<<strvar<<" \n\n";
bvar = false; //bvar.SetValue(false);
ivar = 45; //ivar.SetValue(45);
strvar = "Darth Vader"; //strvar.SetValue("Darth Vader");
std::cout<<"set values: \n"<<bvar<<" "
<<ivar<<" "<<strvar<<" \n\n";
bvar = true; //bvar.SetValue(true);
ivar = 0; //ivar.SetValue(0);
strvar = "Anakin"; //strvar.SetValue("Anakin");
std::cout<<"set again values: \n"<<bvar<<" "
<<ivar<<" "<<strvar<<" \n\n";
return 0;
}

create member function name and call it at runtime in c++

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