Click here to Skip to main content
15,884,473 members
Articles / Programming Languages / C++11
Tip/Trick

Quick and Dirty but Neat-Looking C++ Alternative of C# Properties

Rate me:
Please Sign up or sign in to vote.
4.92/5 (12 votes)
6 Jun 2019CPOL3 min read 24.6K   160   14   26
For those who are searching for a quick template code for the C++ replacement of C# properties, this is it.

Introduction

Even though we all know C# properties are a good replacement of writing getter/setter functions for every single class member field, virtually we don't have a standard way to use properties in C++. Here is a quick implementation of C++ alternative of properties. The design is intended to give a similar-most feeling that you'd have when using C# properties, although there are some nasty aspects in the implementation.

Using the Code

When using the code, just include 'prop.h' at the top of your code. ('prop.cpp' is an example source code.) The design mostly follows the philosophy of C# properties, but there are some minor syntactic details you need to follow.

Declaration

First, if your property doesn't need to implement any special get/set function, then don't.

C++
class Test {
public:
  prop<size_t> p;     //< This is a 'size_t' property.
}

You can use the declared property just like a plain member field. For example:

C++
int foo() {
  Test test;
  test.p = 3;
  std::cout << test.p << std::endl;
}

Second, if your property needs to implement its own getter and setter, feed it the definitions like this. Note that the setter function receives the caller-given value through the reserved keyword, value.

C++
class Test {
  size_t p_org;       //< Let's assume the real value is to be stored here.

public:
  prop<size_t> p {
    __get__() { return p_org; }
    __set__(size_t) { p_org = value; }
  };
};

Finally, you can also declare a read-only property by providing it only a getter function and specifying the original type as const.

C++
class Test {
  size_t p_org;       //< Let's assume the real value is to be stored here.

public:
  prop<const size_t> p_readonly {
    __get__() { return p_org; }
  };
};

If you try to set any value with a read-only property, you'd get the following compile-time error.

C++
int main() {
  Test test;
  test.p_readonly = 0xdeadbeef;  //< compile error: no operator= defined.
  return 0;
}

...

test.cpp:3:19: error: no viable overloaded '='
  test.p_readonly = 0xdeadbeef;
  ~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~

Some important points are below;

  • You need to keep the order of the getter and the setter; you need to declare the getter first.
  • Any property needs to be declared with the primitive types, such as integers or floating points.
    (It assumes the original type has a proper definition of ++, --, and all kinds of compound assignment operators.)
  • Read-only properties must be declared with a const type, and it must be provided with a getter definition.
    (Otherwise, you won't have a way to get a value you want to get anyway!)

Initialization

You can initialize a default property directly with the property's name at the constructor.

C++
class Test {
public:
  prop<size_t> p;
  
  Test() : p(0) {}
};

But in case a property has its own getter/setter, you may initialize the original data member rather than the property itself.

C++
class Test {
public:
  size_t p_org;

  prop<size_t> p {
    __get__() { return p_org; }
    __set__(size_t) { p_org = value; }
  };

  Test() : p_org(0) {}
};

Property Read/Write

Reading from and writing to a property is just like you'd do with regular public member fields. It supports all kinds of operations that can be used on the regular variables. Please refer to 'prop.cpp' in the attachment to see the example read/write usages.

How It Works

Below is the simplified source code of the header 'prop.h'.

C++
#pragma once
#include <cassert>
#include <functional>

template <typename T>
class prop {
public:
  using Getter = std::function<T()>;
  using Setter = std::function<void(T)>;

private:  
  T __val__;
  Getter __getter__;
  Setter __setter__;

  void initialize() {
    __getter__ = [&]() { return __val__; }; 
    __setter__ = [&](T value) { __val__ = value; };
  }

  /* ... */

public:
  prop() { initialize(); }
  prop(T o) : __val__(o) { initialize(); }
  prop(Getter getter, Setter setter = readonly_setter()) :
    __getter__(getter), __setter__(setter) {}

  /* ... */

  virtual prop<T>& operator=(T o)
  { __setter__(o); return *this; }

  virtual operator T() const
  { return __getter__(); }

  /* ... */
};

template <typename T>
class prop<const T> {
public:
  using Getter = std::function<T()>;

private:
  Getter __getter__;

  prop(const prop<T>&) = delete;
  prop(prop<T>&&) = delete;

public:
  prop(Getter getter) : __getter__(getter) {}

  virtual operator T() const
  { return __getter__(); }
};


#define __get__() [&]()
#define __set__(T) , [&](T& value)

Basically, you'd declare the instance of the base class 'prop' and optionally feed it your own getter/setter. The prop class has three members, one (__val__) for the data variable for the default getter/setter and the other two lambda variables (__getter__, __setter__) for the variable manipulation.

What you'd use to specify your own getter/setter (say, __get__() and __set__()) are actually the helper functions written in macro. Specifically, the __get__() macro is the lambda declaration of the getter function, and so is the __set__() macro.

One nasty trick here is putting a comma (,) at the front of the __set__() macro definition, which is simply there to obey the syntax of uniform initialization. I just put a comma there because it would prevent the confusion where programmers need to put an extra comma after the getter definition, and also because it may allow the property declarations to look less awkward, even a little.

Finally, the base class of read-only properties is the partially-specialized prop class (prop<const T>). To be more specific, the read-only prop class specializes any constant type and never defines value-modifying operators. Since it must be provided with a proper getter definition, there is no default constructor defined in this case.

History

  • 2019.05.28: Just posted
  • 2019.05.28: Basic operations (++, --, +=, -=, *=, /=) and read-only properties (allow no __set__() definition)
  • 2019.05.29: Minor compilation bug fixed
  • 2019.05.30: Minor compilation/runtime bug fixed
  • 2019.06.06: Compile-time read-only property feature.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Student Seoul National University
Korea (Republic of) Korea (Republic of)
PhD student in system security. Currently interested in fuzzing. Can't WINE be more user-friendly? :P

Comments and Discussions

 
BugPostfix ++ and -- do not work properly Pin
Сергій Ярошко5-Jun-19 6:22
professionalСергій Ярошко5-Jun-19 6:22 
GeneralRe: Postfix ++ and -- do not work properly Pin
cassert245-Jun-19 20:43
cassert245-Jun-19 20:43 
GeneralRe: Postfix ++ and -- do not work properly Pin
Сергій Ярошко6-Jun-19 1:09
professionalСергій Ярошко6-Jun-19 1:09 
GeneralRe: Postfix ++ and -- do not work properly Pin
cassert246-Jun-19 2:39
cassert246-Jun-19 2:39 
GeneralRe: Postfix ++ and -- do not work properly Pin
Сергій Ярошко6-Jun-19 2:59
professionalСергій Ярошко6-Jun-19 2:59 
GeneralRe: Postfix ++ and -- do not work properly Pin
cassert246-Jun-19 18:40
cassert246-Jun-19 18:40 
GeneralRe: Postfix ++ and -- do not work properly Pin
Сергій Ярошко7-Jun-19 3:44
professionalСергій Ярошко7-Jun-19 3:44 
GeneralRe: Postfix ++ and -- do not work properly Pin
cassert247-Jun-19 4:50
cassert247-Jun-19 4:50 
GeneralRe: Postfix ++ and -- do not work properly Pin
Сергій Ярошко7-Jun-19 5:26
professionalСергій Ярошко7-Jun-19 5:26 
GeneralRe: Postfix ++ and -- do not work properly Pin
cassert247-Jun-19 5:44
cassert247-Jun-19 5:44 
Questiontext formulation Pin
Сергій Ярошко5-Jun-19 5:23
professionalСергій Ярошко5-Jun-19 5:23 
AnswerRe: text formulation Pin
cassert245-Jun-19 20:57
cassert245-Jun-19 20:57 
QuestionAvoid using double underscores Pin
Francis Xavier Pulikotil30-May-19 14:38
Francis Xavier Pulikotil30-May-19 14:38 
AnswerRe: Avoid using double underscores Pin
cassert2430-May-19 15:41
cassert2430-May-19 15:41 
GeneralRe: Avoid using double underscores Pin
Francis Xavier Pulikotil7-Jun-19 10:59
Francis Xavier Pulikotil7-Jun-19 10:59 
AnswerRe: Avoid using double underscores Pin
Marc Clifton5-Jun-19 2:19
mvaMarc Clifton5-Jun-19 2:19 
GeneralRe: Avoid using double underscores Pin
Francis Xavier Pulikotil7-Jun-19 10:56
Francis Xavier Pulikotil7-Jun-19 10:56 
GeneralMy vote of 5 Pin
Jan Heckman30-May-19 0:14
professionalJan Heckman30-May-19 0:14 
GeneralRe: My vote of 5 Pin
cassert2430-May-19 0:35
cassert2430-May-19 0:35 
GeneralRe: My vote of 5 Pin
Jan Heckman2-Jun-19 23:04
professionalJan Heckman2-Jun-19 23:04 
GeneralRe: My vote of 5 Pin
cassert243-Jun-19 1:43
cassert243-Jun-19 1:43 
QuestionDoes not compile Pin
megaadam28-May-19 23:12
professionalmegaadam28-May-19 23:12 
AnswerRe: Does not compile Pin
cassert2429-May-19 2:08
cassert2429-May-19 2:08 
GeneralRe: Does not compile Pin
megaadam29-May-19 2:26
professionalmegaadam29-May-19 2:26 
GeneralRe: Does not compile Pin
cassert2430-May-19 0:29
cassert2430-May-19 0:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.