Emulating the special properties of sizeof with constexpr

后端 未结 4 1388
春和景丽
春和景丽 2021-02-10 21:20

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

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

As well as simply:

<         


        
相关标签:
4条回答
  • 2021-02-10 21:47

    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.

    0 讨论(0)
  • 2021-02-10 21:50

    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.

    0 讨论(0)
  • 2021-02-10 21:53

    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.

    0 讨论(0)
  • 2021-02-10 22:01

    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.

    0 讨论(0)
提交回复
热议问题