Sign in to follow this  
SteveDeFacto

How to use class functions as a variable?

Recommended Posts

With luabind you had to create functions to get and set variables in a class that you wanted to bind. This actually was very useful because it allowed me to create a really handy window class for lua but now I would like to use the class in C++ without needing to use get and set functions every time I want to change something.

Is there a way to use get and set functions as variables? Like this:


class foo
{
public:
void set_val( float in )
{
val = in;
}

float get_val()
{
return val;
}

private:
float val;

};



I want those two functions to act as though I were directly interacting with the variable.

Share this post


Link to post
Share on other sites
That's not possible in standard C++. However, your compiler may have extensions to do that. Ex: MSVC's __declspec(property).

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
That's not possible in standard C++. However, your compiler may have extensions to do that. Ex: MSVC's __declspec(property).


Really? Well I don't really think it would be a good idea to use functions that only work with one compiler since I am hoping to release it as an open source library.

Is there absolutely no other way to trigger a function and or block of code when a variable is set or read from? This actually seems hard to believe...

Share this post


Link to post
Share on other sites
The one way I can think of, which is very ugly, is to create a property class:


// untested
template<typename T>
class property
{
public:
property(const boost::function<T &()> &get,
const boost::function<void (const T &)> &set,
const T &init = T()) :
get_(get),
set_(set)
{
set_(init);
}

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

const operator T &() const { return get_(); }

private:
boost::function<T &()> get_;
boost::function<void (const T &)> set_;
};


But that's ugly and heavy weight and I wouldn't recommend it. I'm showing you to put you off the idea :)

But also, perhaps step back a moment and ask yourself how many times such an interface is really desirable. The thing I don't like about properties is that the syntax suggests a simple, quick operation, when in fact anything could be happening.

I find they can be handy e.g. in Python (where there's first class support for them) on the rare occasion where you start out with a 'public' variable and it later transpires that this variable needs to be part of some invariant that didn't exist before. In this case, you can replace the variable with a property and not break the client interface.

However, would it really hurt you, in this case to simply use a public variable? Ask yourself how many people will be using the code and how widely. And ask yourself about how likely it is that the variable might be involved in some as-yet-unspecified invariant down the road. I'd posit that people put far too much stead in public/private/protected encapsulation and they can sometimes end up creating more of a development burden than would a simple public variable. Not all the time, but sometimes. I get the feeling this could be one of those times.

Share this post


Link to post
Share on other sites
Quote:
Original post by SteveDeFacto
Not for what I was doing. I was not just setting a float or int.


Luabind can call almost arbitrary code for the get/set functionality. It's in the link Kambiz posted.

Share this post


Link to post
Share on other sites
Quote:
Original post by the_edd
The one way I can think of, which is very ugly, is to create a property class:


// untested
template<typename T>
class property
{
public:
property(const boost::function<T &()> &get,
const boost::function<void (const T &)> &set,
const T &init = T()) :
get_(get),
set_(set)
{
set_(init);
}

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

const operator T &() const { return get_(); }

private:
boost::function<T &()> get_;
boost::function<void (const T &)> set_;
};


But that's ugly and heavy weight and I wouldn't recommend it. I'm showing you to put you off the idea :)

But also, perhaps step back a moment and ask yourself how many times such an interface is really desirable. The thing I don't like about properties is that the syntax suggests a simple, quick operation, when in fact anything could be happening.

I find they can be handy e.g. in Python (where there's first class support for them) on the rare occasion where you start out with a 'public' variable and it later transpires that this variable needs to be part of some invariant that didn't exist before. In this case, you can replace the variable with a property and not break the client interface.

However, would it really hurt you, in this case to simply use a public variable? Ask yourself how many people will be using the code and how widely. And ask yourself about how likely it is that the variable might be involved in some as-yet-unspecified invariant down the road. I'd posit that people put far too much stead in public/private/protected encapsulation and they can sometimes end up creating more of a development burden than would a simple public variable. Not all the time, but sometimes. I get the feeling this could be one of those times.


It's a window class that has to interact with the windows API which is not as simple as just setting a variable. Here are some of the functions I turned into variables:


void E_WINDOW::set_title( std::string title )
{
SetWindowText( hWnd, title.c_str() );
}

std::string E_WINDOW::get_title()
{
char* title = NULL;
GetWindowText( hWnd, title, 256 );
return title;
}

void E_WINDOW::set_icon( std::string file )
{
HANDLE icon = LoadImage(NULL, file.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
SendMessage(hWnd, (UINT)WM_SETICON, ICON_BIG, (LPARAM)icon);
IconFileName = file;
}

std::string E_WINDOW::get_icon()
{
return IconFileName;
}

void E_WINDOW::set_left( int left )
{
RECT rect;
GetWindowRect( hWnd, &rect );
SetWindowPos( hWnd, NULL, left, rect.top, (rect.right - rect.left), (rect.bottom - rect.top), NULL);
}

int E_WINDOW::get_left()
{
RECT rect;
GetWindowRect( hWnd, &rect );
return rect.left;
}

void E_WINDOW::set_top( int top )
{
RECT rect;
GetWindowRect( hWnd, &rect );
SetWindowPos( hWnd, NULL, rect.left, top, (rect.right - rect.left), (rect.bottom - rect.top), NULL);
}

int E_WINDOW::get_top()
{
RECT rect;
GetWindowRect( hWnd, &rect );
return rect.top;
}

void E_WINDOW::set_width( int width )
{
RECT rect;
GetWindowRect( hWnd, &rect );
SetWindowPos( hWnd, NULL, rect.left, rect.top, width, (rect.bottom - rect.top), NULL);
SwapChain->Resize();
}

int E_WINDOW::get_width()
{
RECT rect;
GetWindowRect( hWnd, &rect );
return rect.right - rect.left;
}

void E_WINDOW::set_height( int height )
{
RECT rect;
GetWindowRect( hWnd, &rect );
SetWindowPos( hWnd, NULL, rect.left, rect.top, (rect.right - rect.left), height, NULL);
SwapChain->Resize();
}

int E_WINDOW::get_height()
{
RECT rect;
GetWindowRect( hWnd, &rect );
return rect.bottom - rect.top;
}

void E_WINDOW::set_visible( bool visible )
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_VISIBLE);
if (visible)
{
if (!isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style | WS_VISIBLE );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
else
{
if (isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style & ~WS_VISIBLE );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
}

bool E_WINDOW::get_visible()
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_VISIBLE);
return isset;
}

void E_WINDOW::set_maximize( bool maximize )
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_MAXIMIZEBOX);
if (maximize)
{
if (!isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style | WS_MAXIMIZEBOX );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
else
{
if (isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style & ~WS_MAXIMIZEBOX );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
}

bool E_WINDOW::get_maximize()
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_MAXIMIZEBOX);
return isset;
}

void E_WINDOW::set_minimize( bool minimize )
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_MINIMIZEBOX);
if (minimize)
{
if (!isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style | WS_MINIMIZEBOX );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
else
{
if (isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style & ~WS_MINIMIZEBOX );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
}

bool E_WINDOW::get_minimize()
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_MINIMIZEBOX);
return isset;
}

void E_WINDOW::set_buttons( bool buttons )
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_SYSMENU);
if (buttons)
{
if (!isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style | WS_SYSMENU );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
else
{
if (isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style & ~WS_SYSMENU );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
}

bool E_WINDOW::get_buttons()
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_SYSMENU);
return isset;
}

void E_WINDOW::set_caption( bool caption )
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_CAPTION);
if (caption)
{
if (!isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style | WS_CAPTION );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
else
{
if (isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style & ~WS_CAPTION );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
}

bool E_WINDOW::get_caption()
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_CAPTION);
return isset;
}

void E_WINDOW::set_opacity( float val )
{
opacity = val;
SetLayeredWindowAttributes( hWnd, 0, (BYTE)(255 * opacity), LWA_ALPHA );
}

float E_WINDOW::get_opacity()
{
return opacity;
}

void E_WINDOW::set_size( bool size )
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_SIZEBOX);
if (size)
{
if (!isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style | WS_SIZEBOX );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
else
{
if (isset)
{
SetWindowLongPtr( hWnd, GWL_STYLE, style & ~WS_SIZEBOX );
SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DRAWFRAME );
}
}
}

bool E_WINDOW::get_size()
{
long style = GetWindowLongPtr(hWnd, GWL_STYLE);
bool isset = !!(style & WS_SIZEBOX);
return isset;
}

void E_WINDOW::set_show_cursor(bool state)
{
show_cursor = state;
POINT MousePos;
GetCursorPos(&MousePos);
if ( hWnd == WindowFromPoint(MousePos) )
{
if ( state )
{
SetCursor(LoadCursor( NULL, IDC_ARROW ));
}
else
{
SetCursor(NULL);
}
}
}

bool E_WINDOW::get_show_cursor()
{
return show_cursor;
}

void E_WINDOW::set_bgcolor(luabind::object color)
{
bgcolor = D3DXCOLOR( luabind::object_cast<float>(color[1]), luabind::object_cast<float>(color[2]), luabind::object_cast<float>(color[3]), luabind::object_cast<float>(color[4]) );
}

luabind::object E_WINDOW::get_bgcolor()
{
luabind::object table = luabind::newtable( g_Lua );
table[1] = bgcolor.r;
table[2] = bgcolor.g;
table[3] = bgcolor.b;
table[4] = bgcolor.a;
return table;
}





As you could imagine it made working with a window super easy! I also had it initialize the windows with default values which meant I only needed to set values that I wanted different then the default.

Share this post


Link to post
Share on other sites
Quote:
Original post by SteveDeFacto
Quote:
Original post by Kambiz
You can use Luabind properties to bind the variable without writing get set functions.


Not for what I was doing. I was not just setting a float or int.


So you are doing something non trivial but like it to look like a simple assignment in C++? That will just cause confusion, especially if you want to make the code available as a library.

If you want the syntactic sugar implement it in Lua (Luabind can do this) but don't hack it into C++. The easy of use an advantage of Lua, but in C++ one expects the low level interface.

Share this post


Link to post
Share on other sites
Quote:
Original post by SteveDeFacto
It's a window class that has to interact with the windows API which is not as simple as just setting a variable. Here are some of the functions I turned into variables:

*** Source Snippet Removed ***

As you could imagine it made working with a window super easy! I also had it initialize the windows with default values which meant I only needed to set values that I wanted different then the default.


I'd say stick with what you have, especially as some of the functions are non-trivial. The verbosity of C++ is something you'll have to live with.

Share this post


Link to post
Share on other sites
Quote:
Original post by Kambiz
Quote:
Original post by SteveDeFacto
Quote:
Original post by Kambiz
You can use Luabind properties to bind the variable without writing get set functions.


Not for what I was doing. I was not just setting a float or int.


So you are doing something non trivial but like it to look like a simple assignment in C++? That will just cause confusion, especially if you want to make the code available as a library.

If you want the syntactic sugar implement it in Lua (Luabind can do this) but don't hack it into C++. The easy of use an advantage of Lua, but in C++ one expects the low level interface.


Sigh... I think you are being a bit narrow minded. This class is for a high level game engine and would really be doing it's job poorly if it were to require the user to do all the menial low level stuff...

With my class it would be this simple to create a window that would normally be complex to create:

AxWindow* Window = AxCreateWindow(640, 480, "Test");
Window.icon = "foo.ico"
Window.opacity = 0.5f;
Window.show_cursor = false;
Window.bgcolor = RGB(0,0,255);

Share this post


Link to post
Share on other sites
Quote:
Original post by the_edd
Quote:
Original post by SteveDeFacto
It's a window class that has to interact with the windows API which is not as simple as just setting a variable. Here are some of the functions I turned into variables:

*** Source Snippet Removed ***

As you could imagine it made working with a window super easy! I also had it initialize the windows with default values which meant I only needed to set values that I wanted different then the default.


I'd say stick with what you have, especially as some of the functions are non-trivial. The verbosity of C++ is something you'll have to live with.


Are you saying I should stick with get and set functions instead of trying to turn them into variables?

Share this post


Link to post
Share on other sites
Quote:
Original post by SteveDeFacto
With my class it would be this simple to create a window that would normally be complex to create:


AxWindow Window = AxCreateWindow(640, 480, "Test");
Window.SetIcon("foo.ico");
Window.SetOpacity(0.5f);
Window.SetShowCursor(false);
Window.SetBGColor(0,0,255);


Seriously? Is that too complex?

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
Quote:
Original post by SteveDeFacto
With my class it would be this simple to create a window that would normally be complex to create:

*** Source Snippet Removed ***
Seriously? Is that too complex?


It is not complex at all, indeed. But it is counter-intuitive. What parameters come in what sequence? It is evident here, but it could be anything...if you get what I mean. And the Set word is somewhat redundant.

I really like how in D you can call a function without round brackets () if no parameter is required (and I believe when one parameter is required, you can use assignment =). That doesn't need any convention to get it right across the project, and it is very clear what happens.

I admit that functions called like this shouldn't do heavy stuff, as the function call is "small", it calls for something small too.

You can even add the get function at the end of your project, without changing the code that uses it! That's one of the points they thought well of in D (there are more of these examples).

Share this post


Link to post
Share on other sites
Quote:
Original post by Decrius
Quote:
Original post by Sneftel
Quote:
Original post by SteveDeFacto
With my class it would be this simple to create a window that would normally be complex to create:

*** Source Snippet Removed ***
Seriously? Is that too complex?


It is not complex at all, indeed. But it is counter-intuitive. What parameters come in what sequence? It is evident here, but it could be anything...if you get what I mean.

I don't. He's replacing properties with, in general, single parameter functions. There's only a single possible sequence there. If you want to force the user to do things through SetBGColor(RGB(0,0,255)), well, okay -- but that's no less clear than say, SetBGColorRGB(0,0,255).

I suspect most experienced C++ programmers will generally find nontrivial assignments for pod-like types (e.g. show_cursor in your his example) to be extremely counter-intuitive. The amount of boilerplate involves means most eschew it, making it unexpected, which only serves to reinforce the aversion to doing it in the first place, which only serves to make it even more unexpected.

I won't deny it 'looks' cleaner, and I have a healthy appreciation for languages which allow this kind of thing cleanly -- such as Ruby and C#, embracing it with syntax and language constructs respectively -- but I'm quite adverse to doing the same in C++ for the reasons above.

Share this post


Link to post
Share on other sites
Quote:
Original post by SteveDeFacto
Are you saying I should stick with get and set functions instead of trying to turn them into variables?


Yes

Share this post


Link to post
Share on other sites
Quote:
Original post by Decrius
It is not complex at all, indeed. But it is counter-intuitive. What parameters come in what sequence? It is evident here, but it could be anything...if you get what I mean.
Not really, no. If it's a setter, it's pretty much gonna have one argument, whether that's the thing in the parentheses or the thing after the assignment operator. I suppose that doesn't apply to the RGB triplet, but that's purely a matter of personal style whether you have the conversion there or in a constructor expression.
Quote:
And the Set word is somewhat redundant.
No, it describes what's happening. I've seen people leave it out and use overloading to differentiate between getters and setters, but I have to wonder what the purpose of that is. Certainly it doesn't save time unless you're a terrible typist, and having such fundamentally different behavior under different argument lists is pretty contrary to what overloading is for (namely, polymorphism).
Quote:
That's one of the points they thought well of in D (there are more of these examples).
Okay. I think SteveDeFacto is using C++, though.

Share this post


Link to post
Share on other sites
Quote:
Original post by SteveDeFacto
Sigh... I think you are being a bit narrow minded. This class is for a high level game engine and would really be doing it's job poorly if it were to require the user to do all the menial low level stuff...

The class, as others have said, is fine as is. This class should be part of a larger system, possibly within a facade. Perhaps this is something that should ultimately be in a configuration file anyway. After all, what professional-level game is going to hard code the window dimensions? Also, of all the C++ libraries that I use, none of them set and get properties directly. No one will judge you or your library harshly for making them pass parameters.

Wrap it up, call it a day and feel good about a job well done. These are trivialities and will only hold you back from being productive.

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
Not really, no. If it's a setter, it's pretty much gonna have one argument, whether that's the thing in the parentheses or the thing after the assignment operator. I suppose that doesn't apply to the RGB triplet, but that's purely a matter of personal style whether you have the conversion there or in a constructor expression.


The difference is that we now call a _function_ to set the data. The function could conceptually be anything, it's not strict at all...all by convention. But if you treat the variable as a variable, only one possible action is legitimate, or even possible. The function could do anything. I admit this is a very theoretical and perfectism argument...

Quote:
No, it describes what's happening. I've seen people leave it out and use overloading to differentiate between getters and setters, but I have to wonder what the purpose of that is. Certainly it doesn't save time unless you're a terrible typist, and having such fundamentally different behavior under different argument lists is pretty contrary to what overloading is for (namely, polymorphism).


I don't see why overloaded gets/sets are in a disadvantage. The extra / less typing is really not of my concern. It only matters me which is more "perfect", closest to the ideal. I think the setters should have more functional names. SetPosition should be Move, SetRotation should be Rotate.

For the simple gets/sets I do like a variable kind of approach. In C++ this is not possible, but except for the parentheses, the overloaded approach gets pretty close. Sometimes a non-const get is used as a set, I find it really convenient to think about it like variables (like they are!) instead of separate functions that have an _action_ onto that variable (get or set). This added conceptual layer is redundant. There is no need to action a retrieval of the variable, the get variant. It's already there, just use it ;)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this