Reputation: 17265
My library has a several operations that naturally map to arithmetic and logic/boolean operators. However, these operations require additional parameters besides lhs
and rhs
.
Here is a geometric example of what I mean demonstrated with ClipperLib:
Let's say I want to create a polygon offset operator that takes a 2D polygon and an offset delta and performs polygon offsetting. This example is from ClipperLib:
Mathematically, this operation could be mapped to an operator+
taking a polygon-type and a numeric-type.
However, the underlying implementation requires a few more parameters, e.g. mitering, precisions etc.
I want to combine the power, expressiveness and terseness of the math notation with the control and precision of the underlying implementation. I'd like to write:
// somehow materialize a properly parametrized operator+=()
poly += 10;
and have this do just what I want.
There are a few options for injecting these parameters into the binary overloaded operator, but each has its own drawbacks:
Can anyone suggest another approach that may work here?
One option that I am considering but am not yet sure how to make work is to have the overloaded operator as a template function in a separate namespace. The template would be parametrized over the required params (assume they are known at compile time), and once instantiated the operator is somehow pulled into the currently discoverable namespace where the ADL will correctly match it. I am not sure if/how this can be pulled off.
Alternatively, use the same namespace hiding and revealing trick to add automatic type conversions for the operator input types so that they are automatically wrapped with types that provide the extra parameters.
Can this type of namespace hiding and exposing + ADL be done?
I guess this would be sorta like giving the operator a lambda-like behavior.
Upvotes: 2
Views: 115
Reputation: 170064
I warn in advance that my solution is a bit hackish and uscaleable. But if the sole purpose is to reduce the verbosity of the expression for the user.. You can do something akin to the following:
template<class Space>
class numeric
{
long double _val;
public:
numeric (long double val) : _val(val) {}
operator long double() { return _val; }
};
struct s_mither;
numeric<s_mither> operator ""_mtr(long double v) { return v; }
enum class end_token_t : char {};
constexpr end_token_t end_token{};
class entity
{
struct op_params_t
{
long double _scalar;
long double _mither;
bool _in_op;
} op_params;
public:
entity& operator+= (long double scalar)
{ op_params._in_op = true; op_params._scalar = scalar; return *this;}
entity& operator+= (numeric<s_mither> mither)
{ op_params._in_op = true; op_params._mither = mither; return *this;}
entity& operator, (long double scalar)
{ if(op_params._in_op) op_params._scalar = scalar; return *this;}
entity& operator, (numeric<s_mither> mither)
{ if(op_params._in_op) op_params._mither = mither; return *this;}
void operator, (end_token_t)
{ op_params._in_op = false; }
};
int main() {
entity e;
e += 10.0, 1.0_mtr, end_token;
e += 20._mtr, 5, end_token;
return 0;
}
It's ugly, but has the benefit of having all parameters be optional. Sadly I wasn't able to bypass the token.
A better solution will probably be to provide a start token which return a temporary "collector" object on the first comma. That way you can keep all the comma operator overloading outside of your actual entity class.
Upvotes: 1
Reputation: 249153
I would aim to support something like this:
poly += miter(10).limit(3)
That is:
class modifier
{
public:
virtual polygon& add(polygon&) const = 0;
};
class miter : public modifier
{
public:
miter(int width);
miter& limit(int ml);
virtual polygon& add(polygon&) const override;
};
polygon& operator +=(polygon& poly, const modifier& mod)
{
return mod.add(poly);
}
If you want to apply the same operation to several polygons, you can do this:
miter mod(10);
mod.limit(3);
polyA += mod;
polyB += mod;
polyC += mod;
Upvotes: 2