Home > database >  How do I simulate C#'s {get; set;} in C ?
How do I simulate C#'s {get; set;} in C ?

Time:06-16

I am experimenting with lambda functions and managed to recreate a "get" functionality in C . I can get the return value of a function without using parentheses. This is an example class, where I implement this:

using namespace std;

struct Vector2 {
    float x;
    float y;
    float length = [&]()-> float {return sqrt(x * x   y * y); }();
    float angle = [&]()-> float {return atan2(y, x); }();

    Vector2() : x(0), y(0) {}
    Vector2(float a, float b) : x(a), y(b) {}
    ~Vector2() {}
    Vector2(Vector2& other) : x(other.x), y(other.y) {}
    Vector2(Vector2&& other) = delete;
    void operator =(Vector2&& other) noexcept{
        x = other.x;
        y = other.y;
    }
};

int main()
{
    Vector2 vec = Vector2(10, 17);
    printf("%f\n%f\n%f\n%f\n", vec.x, vec.y, vec.length, vec.angle);
}

However, I am currently trying to also recreate the "set" functionality that C# has. But I'm failing. I tried to add this:

void angle = [&](float a)->void {
    float l = length;
    x = cos(a) * l;
    y = sin(a) * l;
};

But am getting "Incomplete type is not allowed" error. I'm not sure if that's how it should look, even if I wasn't getting the error. Is it even possible to recreate the "set" functionality C# has in C ?

I know that I can just use a method SetAngle(float a){...}, but that's not really the point.

CodePudding user response:

TL;DR: Don't

What you have isn't a getter, it's just a normal data member that's calculated once when the object is initialized.

In general, C doesn't support C#-style properties. The usual C -style solution is to just use a pair of member functions (and maybe a data member, if you need to save the value separately), i.e.

struct Vector2 {
    // ...

    float length() const { return sqrt(x * x   y * y); }
    void length(float l) {
        float angle = angle();
        float new_x = l * cos(angle);
        float new_y = l * sin(angle);
        x = new_x;
        y = new_y;
    }

    // ...
};

You can get something close to a C#-style property, but you'll always run into edge-cases where they don't work perfectly. For example, here's something that will work in many cases:

template <typename T>
class Property
{
private:
    std::function<T()> getter_;
    std::function<void(const T&)> setter_;

public:
    Property(std::function<T()> getter, std::function<void(const T&)> setter)
        : getter_{getter},
          setter_{setter}
    {}

    operator T()
    {
        return getter_();
    }

    const T& operator=(const T& val)
    {
        setter_(val);
        return val;
    }
};

struct Vector2
{
    float x;
    float y;
    Property<float> length{
        [this]() { return sqrt(x * x   y * y); },
        [this](float l) {
            float new_x = l * cos(angle);
            float new_y = l * sin(angle);
            x = new_x;
            y = new_y;
        }
    }

    Property<float> angle{
        [this]() { return atan2(y, x); },
        [this](float a) {
            float l = length;
            x = cos(a) * l;
            y = sin(a) * l;
        }
    }

    // ...
};

int main() {
    Vector2 v;
    v.x = 1;
    v.y = 1;

    v.angle = std::numbers::pi / 2;
    std::cout << "(" << v.x << ", " << v.y << ")\n";
}

But this still falls apart in the edge cases, especially when you mix it with templates and/or auto type-deduction. For instance:

Vector2 v;
v.x = 1;
v.y = 1;

auto old_angle = v.angle;
v.angle = std::numbers::pi / 2;

// oops, this prints pi/2, not pi/4 like you probably expected
// because old_angle isn't a float, it's a Property<float> that
// references v
std::cout << old_angle << '\n';

Note also that there's a bug here. Consider this:

int main() {
    Vector2 v1;
    v1.x = 1;
    v1.y = 1;

    Vector2 v2 = v1;
    v2.angle = std::numbers::pi / 2;

    // Oops, assigning to v2.angle modified v1
    std::cout << "(" << v1.x << ", " << v1.y << ")\n";
}

You could work around these issues by making Property non-copyable, but then you force any class that uses it to implement a custom copy-constructor. Also, while that would make the auto case "safe", it does so by turning it into a compile error. Still not ideal.

CodePudding user response:

I agree with Miles. This is not the greatest idea, because it's unnatural for C developers, and you should write code that is first and foremost easy to read.

However, as an engineering challenge, here's a possible implementation:

#include <math.h>
#include <iostream>

template <typename T>
class Member
{
public:
    operator T() const { return _value; }
    void operator =(const T& value) const { _value = value; }                                                                                                                                                             void operator =(T&& value) { _value = std::move(value); }
                                                                                                                                                                                                                      private:
    T _value;
};

class Angle
{
public:
    Angle(const Member<float>& x, const Member<float>& y) :
        _x(x), _y(y) {}

    operator float() const { return atan2(_y, _x); }

private:
    const Member<float>& _x, _y;
};

class Obj
{
public:
    Member<float> x, y;
    Angle angle;

    Obj() : angle(this->x, this->y) {}
};

int main()
{
    Obj o;
    o.x = 3;
    o.y = 5;
    std::cout << o.x << ", " << o.y << " -> " << o.angle << std::endl;
}
  •  Tags:  
  • c
  • Related