Giter Site home page Giter Site logo

self_macro's Introduction

self_macro

C++14 header only library that exposes a macro that creates a type alias for the current class without naming it.

Also exposes macros that let you associate two types such that you can retrive the second type with the first.

For example:

struct struct_name {
    SELF_MACRO_GET_SELF(type_alias_name, public);
    static_assert(std::is_same_v<type_alias_name, struct_name>);
};

May be useful for code generation via other macros.

Usage

#define SELF_MACRO_DEFINE_SELF(NAME, ACCESS)

NAME should be an id, and ACCESS one of private, protected or public. Equivalent to ACCESS: using NAME = <self>, where <self> is the type of the current class.

store<Tag, ToStore>()

namespace self_macro {

template<typename Tag, typename ToStore>
constexpr void store() noexcept;

template<typename Tag, typename ToStore, typename T>
constexpr T&& store(T&& value) noexcept;

}

Associates ToStore with Tag when this template is instantiated.

If passed an argument, return that same argument. Otherwise, returns nothing.

typename retrieve<T>

namespace self_macro {

template<typename Tag>
using retrieve = /* ... */;

}

If a type has been previously associated with the given Tag, this will be an alias for that type. If there is no association or more than one association, a compile time error will occur.

typename store_with_type<Tag, ToStore, Result>

namespace self_macro {

template<typename Tag, typename ToStore, typename Result = ToStore>
using store_with_type = /* ... */ Result;

}

Like store but evaluates to a type so that it can be used in contexts that require a type: An alias for Result that associates ToStore with Tag when instantiated.

#define SELF_MACRO_WRAP(...)

Wraps a variadic argument naming a type so it is safe to use as a single macro argument. E.g., std::array<int, 5> would be two macro arguments, std::array<int and 5>, but SELF_MACRO_WRAP(std::array<int, 5>) would name the same type but be passed as one macro argument.

#define SELF_MACRO_STORE_TYPE_DECL(TAG, TOSTORE)

Expands to a (static_assert) declaration that associates the type named by TOSTORE to the type named by TAG when instantiated. (Variadic, so no need to wrap either type name)

#define SELF_MACRO_STORE_TYPE_EXPLICIT_INST(TAG, TOSTORE)

When placed in the :: namespace (at file scope), expands to an explicit template specialisation that associates the type named by TOSTORE with the type named by TAG. In particular, this means that access checks are not done in any subexpression.

How?

It is not easy to get the type of the enclosing struct at some points in the struct without explicitly naming it. One way is to use this which is of type "pointer-to-struct". However, this can only be used in two circumstances:

  1. In the declaration and definition of non-static member functions at any point after the cv-qualifiers
  2. Within a default member initializer

These look like:

struct S {
    int a;
    int b /* this cannot be used here */ = /* this can be used here */ this->a;

    int f(int x) /* this cannot be used up to here */ & /* this can be used here */ noexcept(noexcept(this)) {
        return x + this->b;
    }
    auto g(int y) /* this can be used here */ -> decltype(this->b + y) {
        return this->b + y;
    }

    /* this cannot be used here */
    // using self = std::remove_pointer_t<decltype(this)>;
    // Nor in static member functions
};

One way to get the magic "self" type is to take the type of this from a place it is allowed (say a trailing return type) and get its value in the place it's needed (to make a type alias).

This can be achieved similar to how private members can be accessed using an explicit instantiation, although we only need an implicit instantiation here to get access to decltype(this).

#include <type_traits>

// Or use C++20 std::type_identity
template<typename T> struct type_identity { using type = T; };

template<typename Tag>
struct retrieve_type {
    // This auto placeholder is resolved later (which is why C++14 is needed)
    friend constexpr auto retrieve_type_friend(retrieve_type<Tag>);

    friend auto get() {
        return retrieve_type_friend(*this);
    }
};

template<typename Tag, typename Stored>
struct store_type {
    // This will resolve the auto for retrieve_type_friend(retrieve_type<Tag>),
    // which will be found by `retrieve_type<Tag>::get` to get `Stored`
    friend constexpr auto retrieve_type_friend(retrieve_type<Tag>) {
        return std::type_identity<Stored>{};
    }
};

// Implicitly instantiate `store_type<TAG, TYPE>` to make
// `retrieve_type<TAG>::get` be `type_identity<TYPE>`
#define STORE_TYPE(TAG, TYPE) static_cast<void>(store_type<TAG, TYPE>{})
// Retrieve `TYPE` from `type_identity<TYPE>`
#define RETRIEVE_TYPE(TAG) typename decltype(retrieve_type<TAG>::get())::type

// Now we only need to store the type at a place we can use `this` and
// retrieve it outside. For the tag, use a nested class with an unused name
// (Here, both the function and the tag are called `_`)
#define DEFINE_SELF(NAME, ACCESS) \
private: \
    struct _; \
    auto _() -> decltype( \
        STORE_TYPE(struct _, typename std::remove_pointer<decltype(this)>::type) \
    ); \
ACCESS: using NAME = RETRIEVE_TYPE(struct _)

// Calling `DEFINE_SELF(NAME, ACCESS)` inside a struct `S` will
// store `S` to the tag `S::_`, and `_` can easily be named inside the class

The above is a minimal snippet for the DEFINE_SELF macro (called SELF_MACRO_DEFINE_SELF in this project's header file).

To make this C++11 compatible, we would have to be able to get rid of the placeholder type in retrieve_type_friend.

This could be done by declaring it like template<typename T> T retrieve_type_friend(retrieve_type<Tag>), and having the definition give T a default value of Stored. However, this is non-standard, as a template friend declaration with default arguments can only be declared once.

Alt uses

The names of anonymous classes and unions can be retrieved outside the struct:

struct S {
    struct tag;
    union {
        int x = /* this here refers to the type of the anonymous union */
            self_macro::store<
                tag,
                std::remove_pointer<decltype(this)>::type
            >(0);  // tag is now associated with the unspeakable type!
    };
    int a;
    // Can't use `self_macro::retrieve<tag>` here because x's
    // initializer hasn't been instantiated yet (not a complete-class context)
};


using anonymous_union_type = self_macro::retrieve<S::tag>;
static_assert(sizeof(anonymous_union_type) == sizeof(int));
static_assert(sizeof(S) == 2 * sizeof(int));
// anonymous_class_type is "S::<unnamed union>"

(It can also be named by simply calling a function in an initializer, like int x = f<decltype(this)>(), but it can't be smuggled out further)

Whether or not anonymous union types exist doesn't seem entirely clear. The standard says the names of members exist solely in the scope of the enclosing type, so it might be that it declares a type with no members (which is a confusing defintion), or that no type exists and this should refer to the enclosing type (which is how MSVC implements anonymous unions).


You can also use this as a general mapping structure, for example:

// map_key<K> is the key to the map by being a unique type for each key
template<int K>
struct map_key : std::integral_constant<int, K> {};

static_assert((
    self_macro::store<map_key<0>, signed char>(),
    self_macro::store<map_key<1>, short>(),
    self_macro::store<map_key<2>, int>(),
    self_macro::store<map_key<3>, long>(),
    self_macro::store<map_key<4>, long long>(),
    true
), "");

template<int i>
using get_from_map = self_macro::retrieve<map_key<i>>;

static_assert(std::is_same<get_from_map<0>, signed char>::value, "");
static_assert(std::is_same<get_from_map<3>, long>::value, "");

Try it on Compiler Explorer

Using a wrapper around type_identity for type keys and having integral_constant values for non-type values.


You can also reimplement the private member accessor with these:

// Setup: explicitly instantiate to store the type
template<typename Tag, typename Type, Type Value, self_macro::store_with_type<Tag, std::integral_constant<Type, Value>, std::nullptr_t> = nullptr>
struct store_value {};

// Type with private member we want to access
struct v {
    constexpr int get_x() const { return x; }
private:
    int x = 4;
};

// Explicitly instantiate `store_value` to map `v_x_tag` to
// an integral_constant holding `&v::x`
struct v_x_tag;
template
struct store_value<v_x_tag, decltype(&v::x), &v::x>;

// Or use the convenience macro:
// SELF_MACRO_STORE_TYPE_EXPLICIT_INST(v_x_tag, std::integral_constant<decltype(&v::x), &v::x>);

// Retrieve that value
constexpr int v::* v_x = self_macro::retrieve<v_x_tag>::value;


// Test it
constexpr v get_default() {
    v a;
    return a;
}
constexpr v get_modified(int to) {
    v a;
    a.*v_x = to;
    return a;
}

static_assert(get_default().get_x() == 4, "");
static_assert(get_modified(7).get_x() == 7, "");

Try it on Compiler Explorer

(Though with a C++14 requirement over a C++11 requirement)

self_macro's People

Contributors

mitalashok avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.