Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Emulating the special properties of sizeof with constexpr

Tags:

c++

In C++ sizeof is somewhat unique in that it's legal to write this:

int x;
sizeof(x); // a variable

As well as simply:

sizeof(int); // a type

(There's a third even weirder variant I'd rather ignore for now though, with no parenthesis needed, since I'm pretty certain that is impossible to emulate)

I'd like to be able to replicate this behaviour myself. To motivate it I've got an example bitsof operator.

#include <climits>

template <typename T>
struct bits_traits {
  enum { value = sizeof(T) * CHAR_BIT };
};

struct int_12_bit {
  enum { bits = 12 };
  // Let's pretent this has a bunch of code for looking and feeling like a 12bit int in a helpful and portable way
};

template <>
struct bits_traits<int_12_bit> {
  enum { value = int_12_bit::bits };
};

#define bitsof(x) bits_traits<x>::value

int main() {
  using std::size_t;
  size_t b = bitsof(int);
  size_t a = bitsof(int_12_bit);

  int_12_bit x;
  size_t c = bitsof(x); // <-- Not cool
}

Clearly I could have written the whole thing in terms of a macro, using sizeof, e.g.

#define bitsof(x) (sizeof(x) * CHAR_BIT)

But then I lose the ability to "specialise" it.

And equally I could write size_t c = bitsof(decltype(x)). However what I'm asking here is for a way of emulating that behaviour in my own code without having to settle for a workaround. How can I write a bitsof that looks and feels like sizeof, but specialises like traits? Do I just have to accept that sizeof is a bit special and live with it?

I initially played with a few ideas:

  1. Perhaps decltype works like sizeof, e.g. decltype(0) and decltype(int) are synonymous. No luck there though.
  2. Maybe we could do something with pointer/reference template parameters. I couldn't see a way of getting deduction to work properly for that case though, and it would impose additional constraints on what variables we could use bitsof with.
  3. Maybe some crazy SFINAE with a combination of templates and macros, but I can't see a way of making that happen, it's always just a syntax error.
  4. Possibly something to workaround the limitations of one of the above using GCC's statement-expr extension.

As there's an easy workaround with decltype and more of a learning experiment I'm open to ideas using anything available in any C++ released compiler targeting any past, present or future standard.

like image 214
Flexo Avatar asked Dec 10 '22 03:12

Flexo


1 Answers

You can do something like this:

#include <type_traits>

#define bitsof(k) decltype(bitsof_left+(k)+bitsof_right)

template <class K>
struct bits_traits { /* whatever you want here */ };

struct bitsof_left_t {
    template <class T>
    bits_traits<T> operator+(const T&);
} bitsof_left;

struct bitsof_right_t {
    template <class T>
    friend T operator+(const T&, bitsof_right_t);

    bitsof_right_t operator+();

    template <class T>
    operator T() const;

} bitsof_right;

int main()
{
    using foo = bitsof(42);
    using bar = bitsof(int);

    static_assert(std::is_same<foo, bits_traits<int>>::value);
    static_assert(std::is_same<bar, bits_traits<int>>::value);
}

It works like this.

a + (42) + b is parsed as (a + (42)) + b), then overloaded binary operator+ at either side kicks in. In my example the operators are only declared, not defined, but since it's unevaluated context, it doesn't matter.

a + (int) + b is parsed as a + ((int) (+ b)). Here we employ the overloaded unary + at the right side, then overloaded cast operator, then overloaded binary + at the left side.

like image 93
n. 1.8e9-where's-my-share m. Avatar answered Jan 19 '23 00:01

n. 1.8e9-where's-my-share m.