So thanks to C++11, it's now possible to combine macros, user-defined literals, lambdas, etc. to create the closest I can get to 'syntactic sugar'. An example would be
if (A contains B)
Of course this is easy.
cout <<("hello"_s contains "ello"_s)<<endl;
The expression converts to a bool, where contains is a custom struct that takes the left-hand side and right-hand side as arguments. The struct of course overloads operator+ to take the custom string literal first, returning itself, then the operator+ for the struct itself.
struct contains_struct {
string lhs;
string rhs;
void set_lhs(string lhs) { this->lhs = lhs; }
void set_rhs(string rhs) { this->rhs = rhs; }
operator bool() const {
return string::npos != lhs.find(rhs);
}
} contains_obj;
contains_struct& operator+(const string& lhs, const contains_struct& rhs) {
contains_obj.set_lhs(lhs);
return contains_obj;
}
contains_struct& operator+(const contains_struct& lhs, const string& rhs) {
contains_obj.set_rhs(rhs);
return contains_obj;
}
#define contains +contains_obj+
Now I decided I want to go further. What about
(x in a) perform cube
It's no list comprehension, but it's a pretty good example right? At first I said, well I'd have to go to stackoverflow to ask about custom operator precedence, but it's straight forward to put it in parentheses since no one in their right mind would use my code. Instead, I expanded upon my other example and have 'in' and 'perform' as custom structs, just like 'contains'.
You can go further and template it so that x can be any numerical index, and a as any container, but for simplicity, I left x as an integer and a as a vector of ints. Now so far it doesn't actually take the local variable x as an argument, it uses it locally in the operator string() function.
To simplify things, I store the results of the expression in a string, like so
operator string() const {
string s = "";
for (int x : lhs.rhs)
s += to_string(rhs(x)) + string("\n");
return s;
}
Thanks to another question: Overloading assignment operator for type deduction
I realized one practical use for returning it as an assignment is the following:
struct result_struct {
vector<int> results;
result_struct(vector<int> results) { this->results = results; }
};
...
operator result_struct() const {
vector<int> tmp;
for (int x : lhs.rhs)
tmp.push_back(rhs(x));
return result_struct(tmp);
}
...
result_struct result_2 = (x in a) perform cube;
for (int x : result_2.results)
cout <<x<<endl;
Thanks to milleniumbug's answer, I can do:
struct for_obj
{
int _lhs;
std::vector<int> _rhs;
for_obj(int lhs, std::vector<int> rhs)
: _lhs(lhs), _rhs(rhs) { }
};
INFIX_OPERATOR(for_obj, in_op, int, std::vector<int>)
{
return for_obj(lhs(), rhs());
}
#define in + in_op() +
INFIX_OPERATOR(int, perform_op, for_obj, std::function<int(int)>)
{
for (int i = 0; i < lhs()._rhs.size(); i++)
rhs()(lhs()._rhs[i]);
return 0;
}
#define perform + perform_op() +
There are two caveats. First, I return an int so that I can assign it to a dummy variable to get it to execute. I could always do the result_struct thing I did before, or return a std::function object to call it by itself, but I'd be repeating myself. The other caveat is that because there are so many consts in the macro, you cannot modify the lhs (which doesn't allow you to specifiy an iterator).
All things considered, the following works as expected.
int x = 0;
std::vector<int> nums = { 1, 2, 3 };
auto cube = [] (int x)
{
std::cout << x * x * x << std::endl;
return x * x * x;
};
int i = (x in nums) perform cube;
New version
class PerformObj {
int counter;
public:
PerformObj() : counter(0) { }
~PerformObj() { }
InObj lhs;
std::function<int(int)> rhs;
operator int() const {
return rhs(lhs.rhs[counter]);
}
} performobj;
#define perform + performobj +
PerformObj& operator+(const InObj& lhs, PerformObj& rhs) {
rhs.lhs = lhs;
return rhs;
}
PerformObj& operator+(PerformObj& lhs, const std::function<int(int)>& rhs) {
lhs.rhs = rhs;
return lhs;
}
int main()
{
std::vector<int> nums = {1,2,3};
int x = 0;
auto cube = [] (int n) {
return n * n * n;
};
std::cout << x in nums perform cube << std::endl;
}
explicit operator std::vector<int>() const {
std::vector<int> temp;
for (int i = 0; i < lhs.rhs.size(); i++) {
temp.push_back(rhs(lhs.rhs[i]));
}
return temp;
}
int y = 0;
std::cout << y in static_cast<std::vector<int>>(x in nums perform cube) perform std::function<int(int)>([] (int i) -> int {
return i;
}) << std::endl;
Should I make it so that instead of infix operators, there are postfix operators, like "String literal"s.contains "Other string literal"s
, or do it function style, "String literal"s.contains("Other string literal"s)
?
How would I improve my code to make it more extensible? As it is right now, it's very polluted. Is there a better/more generalized/less clunky way to do this? For example, to generalize the expressions so that I don't need define statements or to reuse code.