3

I am looking for space efficient implementation of optional (sizeof small_optional<T> == sizeof (T)). So the emptiness is encoded using some special value of T, for example

small_optional<int, -1> 

requires that I never store -1 in the small_optional so -1 can be used as magic value to determine if optional is empty or not.

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
  • I've never come across anything like this. The nearest thing I guess is `npos` in the C++ standard library. Out of interest, what would happen to 2 - 3 in your particular case, or is your class limited to storage and not arithmetic operations? – Bathsheba Jul 03 '20 at 09:48
  • 1
    boost does this for reference types. optional value is stored as null – user7860670 Jul 03 '20 at 10:00
  • @Bathsheba UB would happen... :) But in some cases it is valuable, for example index of an array(I can use uint32_t max as magic values if I know my arrays are smaller than 4Gitems) – NoSenseEtAl Jul 03 '20 at 10:18
  • 1
    @dfri sorry, forgot to accept after I upvoted. :D – NoSenseEtAl Jul 20 '20 at 16:09

1 Answers1

3

The markable lib was created for this sole purpose:

Markable 1.0.0

An alternative to boost::optional<T> which does not store an additional bool flag, but encodes the 'empty' state inside T using a special indicated value.

Usage

Do you want to store a possibly missing int? Can you spare value -1? You can use it like this:

using namespace ak_toolkit;
typedef markable<mark_int<int, -1>> opt_int;

opt_int oi;
opt_int o2 (2);

assert (!oi.has_value());
assert (o2.has_value());
assert (o2.value() == 2);

static_assert (sizeof(opt_int) == sizeof(int), "");

Do you want to store a possibly missing std::string, where 'missing' != 'empty'? Can you spare some string values that contain a null character inside, like std::string("\0\0", 2)? This is how you do it:

struct string_marked_value                           // a policy which defines the representaioion of the
  : ak_toolkit::markable_type<std::string>           // 'marked' (special) std::string value
{               
  static std::string marked_value() {                // create the marked value
    return std::string("\0\0", 2);
  }
  static bool is_marked_value(const std::string& v) { // test if a given value is considered marked
    return v.compare(0, v.npos, "\0\0", 2) == 0;
  }
};

typedef ak_toolkit::markable<string_marked_value> opt_str;
opt_str os, oE(std::string(""));

assert (!os.has_value());
assert (oE.has_value());
assert (oE.value() == "");

static_assert (sizeof(opt_str) == sizeof(std::string), "");

Albeit not a Boost library, markable is licensed under Boost Software License, Version 1.0., and is even referred to from the Performance Considerations section of Boosts own optional type:

Performance considerations

[...]

Controlling the size

[...] Therefore, if the size of the objects is critical for your application (e.g., because you want to utilize your CPU cache in order to gain performance) and you have determined you are willing to trade the code clarity, it is recommended that you simply go with type int and use some 'magic value' to represent not-an-int, or use something like markable library.


The background and thought process of the lib is explained in the following blog post from the lib's author:

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Since the special value is stored inside the marked object, does it mean it doubles the memory usage? – luizfls Aug 18 '20 at 20:15
  • 1
    @luizfls No. The special value is encoded in _the type_, not duplicated for every object of said type. For e.g. an integral type this is zero-cost as the special value can be encoded as a non-type template parameter in the encoded type. For e.g. a markable `std::string` the memory cost will be a single `std::string` object (stored in a .bss segment) for _the type_, but this will not duplicate over several instances of the type. This approach is, in essence, zero-cost w.r.t. extra memory usage but at the cost of worse semantics. – dfrib Aug 19 '20 at 06:53
  • Alright! I didn't realize template parameters were stored at "type level" rather than "object level"; neat! – luizfls Aug 19 '20 at 08:35