Emulating the special properties of sizeof with constexpr

if constexpr
c++ constexpr std::array
c++ detection idiom
std::is_detected
constexpr array
size of constexpr array
c type traits has member function
sfinae decltype

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.

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.

Emulating the special properties of sizeof with constexpr, Emulating the special properties of sizeof with constexpr. 由依然范特西╮ 提交于2019-12-03 08:26:41. In C++ sizeof is somewhat unique in that it's legal to write� A constexpr specifier used in a function or static member variable (since C++17) declaration implies inline. If any declaration of a function or function template has a constexpr specifier, then every declaration must contain that specifier. A constexpr variable must satisfy the following requirements: its type must be a LiteralType.

Its hard and probably impossible, mainly because you can only pass compile-time constants as template values to templates, hence your last example with the int_12_bit x; will never be able to be a template value (and types can't be passed as parameters, of course). I played around a bit with decltype, declval and different templates, but I simply could not get it to take in types and (non-constand expression) values with a single "call". It's really unfortunate decltype doesn't accept types, I wonder why the committee choose to only accept expressions.

Since you mentioned gcc-extensions, there is an extension which can make it work, __typeof__.

I personally have never used this extension, but it seems like it works similar to decltype but it also accepts types directly.

This snipped compiles under gcc x86-64 8.3 for me:

template<typename T>
struct bits_trait;

template<>
struct bits_trait<int>{};

void f() {
    int x;
    bits_trait<__typeof__(x)>();
    bits_trait<__typeof__(int)>();
}

But this will only compile under gcc.

Edit: Clang seems to support it as well, no luck with MSVC though.

Ben's Blog, For now, though, we emulate concepts using the detection idiom. All current iterations of the detection idiom depend on SFINAE, which is the property that certain errors U> static constexpr decltype(std::declval<U>().get(), bool()) test_get(int) We can, however, fairly easily emulate this by abusing sizeof . Project => Properties => C++ => Language => Enable Run-time Type Information: Yes (-frtti) Just tried this; it compiles cleanly with Visual Studio 2015 Update 2 (+ the recent patch), and Clang 3.7 with Microsoft CodeGen (the latest update), and -frtti

Not considering macros and without decltype, it is simply not possible because of the language syntax.

However you can get pretty damn close:

template <class T>
constexpr auto bitsof(T) { return sizeof(T) * CHAR_BIT; }

template <>
constexpr auto bitsof(int_12_bit) { return 12; }

template <class T>
constexpr auto bitsof() { return sizeof(T) * CHAR_BIT; }

template <>
constexpr auto bitsof<int_12_bit>() { return 12; }
auto test()
{
    constexpr int a{};
    constexpr int_12_bit x{};

    static_assert(bitsof(a) == 32);
    static_assert(bitsof(x) == 12);

    static_assert(bitsof<int>() == 32);
    static_assert(bitsof<int_12_bit>() == 12);
}

Aside from the slightly different syntax (but c'mon it's so close it shouldn't really matter) the biggest difference to the sizeof is that the arguments are not in an unevaluated context. So bitsof(foo()) will call foo(). And bitsof(a) is UB if a is uninitialized.

The constexpr array size problem, #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) { return N; }. This beats the C macro The specific rule we're violating is [expr.const]/4.12. One of the big features of span<T> is that all contiguous ranges over T are convertible to it. namespace impl { constexpr int myPow(int a, int b = 0){ return b == 0 ? 1 : myPow(a,b - 1) * a; } } constexpr int myPow(int a, int b){ return impl::myPow(a,b); } If and when you upgrade at least to gcc 4.7.2 you would be able with -std=c++11 even to hide that auxiliary within myPow itself, because you will be allowed to define types within the

Building upon the quite magical answer from n.m., with just tiny bit of massage, it seems it is possible to have bitsof mimic sizeof.

#include <climits>
#include <iostream>
#include <type_traits>

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

struct int_12_bit {
  enum { bits = 12 };
};

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

#define bits_traits_of(k) decltype(bits_traits_of_left+(k)+bits_traits_of_right)

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

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

    bits_traits_of_right_t operator+();

    template <class T>
    operator T() const;

} bits_traits_of_right;

#define bitsof(x) bits_traits_of(x)::value

int main() {
  using std::size_t;
  size_t a = bitsof(int);
  size_t b = bitsof(int_12_bit);
  std::cout <<"a="<< a <<", b="<< b << std::endl;

  int_12_bit x;
  size_t c = bitsof(x);
  std::cout <<"c="<< c << std::endl;
}

The only thing that I changed, other than adding in definitions for bits_traits, is to redefine bitsof so that it returns the bits_traits::value rather than the bits_traits type.

$ ./a.out 
a=32, b=12
c=12

I'm just writing this up to verify that it can work. All credits should go to n.m.'s answer.

[PDF] Emulating C++0x Concepts, in generic libraries by emulating several features of the proposed C++0x exten- sions. The compiler chooses the overload based on the most special- ized ( refined) affect the size of constrained classes, leading to larger runtime objects. 4.1.2. struct is_same<T, T> { static constexpr bool value = true; };. The type trait is� constexpr int a = 2; Static specifies the lifetime of the variable. A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later. So, what does constexpr mean? constexpr: compile time

Programming Guide :: CUDA Toolkit Documentation, Query L2 cache Properties The GPU is specialized for highly parallel computations and A thread block size of 16x16 (256 threads), although arbitrary in this The CUDA frontend compiler aims to emulate the host compiler The experimental nvcc flag --expt-relaxed-constexpr removes this restriction. Both the size calculation and array filling are simplified since they’re not that important. First attempt to constexpr. In the current (C++17) standard there is no such thing as the constexpr std::vector<T>, but there is a std::array<T, size>.

CXX11Meta.h, Emulate the cxx11 functionality that we need if the compiler doesn't support it. file expands upon Core/util/Meta.h and adds support for C++11 specific features. */ template<typename tt> struct type_list { constexpr static int count = sizeof. A constexpr function is defined like any other function but must meet certain restrictions: The return type and the type of each parameter in must be a literal type (§2.4.4, p. 66), and the function body must contain exactly one return statement

gnzlbg/static_vector: A dynamically-resizable vector with , Features → 4.1 Storage/Memory Layout; 4.2 Move semantics; 4.3 constexpr support A static_vector can also be poorly emulated by using a custom allocator, like for to be implemented as a special case of std::vector with a custom allocator. been moved from state), the size of a is not altered, and a. size() == b.size() . constexpr functions will be evaluated at compile time when all its arguments are constant expressions and the result is used in a constant expression as well. A constant expression could be a literal (like 42), a non-type template argument (like N in template<class T, size_t N> class array;), an enum element declaration (like Blue in enum Color { Red, Blue, Green };, another variable declared

Comments
  • sizeof() is a compiler intrinsic function, the compiler knows the difference between a type and a variable and adjusts its behavior accordingly. You can't always emulate in user code what the compiler can do. This is one of those cases. You will have to stick with using decltype when passing a variable to your template.
  • Otherwise, try changing your bitsof() macro into an overloaded templated function instead, eg: template<typename T> size_t bitsof() { return bits_traits<T>::value; } template<typename T> size_t bitsof(const T &) { return bits_traits<T>::value; } ... size_t b = bitsof<int>(); ... int_12_bit x; size_t c = bitsof(x); Or don't overload at all: template<typename T> size_t bitsof_type() { return bits_traits<T>::value; } template<typename T> size_t bitsof_variable(const T &) { return bits_traits<T>::value; } ... size_t b = bitsof_type<int>(); ... int_12_bit x; size_t c = bitsof_variable(x);
  • Under the hood, types and expressions are very different. I doubt it's possible to do this without using non-standard extensions like tkausl suggested. If one day decltype accepts types directly, it would be trivial. We can only dream.
  • Not sure what you're referring to with the "third even weirder variant" comment. sizeof is an operator; the parentheses are superfluous in all cases (notwithstanding precedence considerations, as is usual for any expression). sizeof(int) is sizeof (int) is sizeof int. It ain't a function.
  • @LightnessRacesinOrbit int x = sizeof int; won't compile and isn't legal. int x = sizeof x; is legal. Does that not seem even a little bit weird? With a blank slate designing a language I'd make the parenthesis mandatory.
  • Has this trick been documented before? Or is it your idea? Nice!
  • This looks like black magic. How is something like decltype(5+(int)+5) parsed? Very cool.
  • It does look like black magic! It seems the (int)+bitof_right is first parsed by bitsof_right_t operator+(); to get rid of the + (?), then by operator T() const; into nothing (?). I'm not sure... would really like a definitive explanation.
  • @Edy The expression bitsof_right is of type bitsof_right_t which supports unary plus, so +bitsof_right is just a bitsof_right_t (of unknown value due to missing operator definition, but that doesn't matter since this is an "unevaluated context"), which is then cast to int. So you've ended up with decltype(bitsof_left + int()) and you can apply a similiar direction of logic to evaluate the rest. This is really clever. Different but not dissimilar rules allow the same logic to work when k is an expression rather than a type (via binary +)
  • This is neat and looks nothing like any of the ideas I played with when experimenting on this. I'm tempted to generalise it into an equivalent of decltype with semantics that permits having types as well as expressions as the input.