This is an archive of the discontinued LLVM Phabricator instance.

Add llvm::Any
ClosedPublic

Authored by zturner on Jun 30 2018, 3:42 PM.

Details

Summary

This is a replacement for std::any until such time that we have C++17. While I generally agree it should not be used, and usually you want something like a variant, there are still occasions where it can be useful. One example is where a user wants to attach some user data to a structure that passes through library code and comes back around through a callback or similar mechanism. Another is when porting old-style C code that uses void*. I'm sure we can come up with plenty of other uses as well.

Diff Detail

Repository
rL LLVM

Event Timeline

zturner created this revision.Jun 30 2018, 3:42 PM

Hey Zach - what's your initial use case/motivation for this?

I've wanted this for quite a long time. My original motivation for this was back when i was working primarily in LLDB. There's a lot of places where void* is passed around as a generic parameter to a user callback. So, for example, you say DoSomethingAsynchronously(Callback, Param) and when the thing is finished, it invokes Callback(Param). There are obviously better ways to do this if you're designing this from the beginning, but it's not always easy to retrofit that into an existing design. So I think several places in LLDB could be cleaned up by replacing some void* with Any. Note those places still exist today, so Any could be useful there right now.

More recently while designing a basic API for a tracing library, I've gravitated towards a design where the library generates events (represented by some structure) depending on what happens. But since tracing is heavily platform dependent, there's only so much of this that you can abstract away into a common structure and there will be some additional pieces that only make sense on the particular platform. So you call:

unique_ptr<EventBase> Event = llvm::trace::WaitForEvent();
// Do something depending on what kind of event it is.
llvm::trace::ContinueFromEvent(std::move(Event));

In order for every possible platform to implement this correctly, there may need to be some platform-specific state maintained between the call to Wait and the call to Continue. Linux might need to pass the exact signal value and parameters. Windows might need to pass the exact EXCEPTION_RECORD structure that the OS filled out for us right before we return from WaitForEvent(). It's useful to be able to put that into an Any, so that we have something like:

class EventBase {
  TraceEventType Type;
  uint64_t ThreadId;
  uint64_t ProcessId;
  Any PlatformPiece;
};

class ThreadCreatedEvent : public EventBase {
  void *EntryPoint;
  void *TlsArea;
  // Etc.
};

class BreakpointEvent : public EventBase {
  uint64_t Address;
};

and so forth and so on. When creating this struct before returning from WaitForEvent(), the library can set PlatformPiece to some structure that would contain any platform-specific state needed to properly continue. I prefer not to use inheritance and llvm::cast here (e.g. WindowsBreakpointEvent, PosixBreakpointEvent, etc) because it just leads to inheritance hierarchy explosion and ultimately it's no more type-safe than any (someone still has to make an assumption about exactly what type it is).

Hopefully that all makes sense.

I've wanted this for quite a long time. My original motivation for this was back when i was working primarily in LLDB. There's a lot of places where void* is passed around as a generic parameter to a user callback. So, for example, you say DoSomethingAsynchronously(Callback, Param) and when the thing is finished, it invokes Callback(Param). There are obviously better ways to do this if you're designing this from the beginning, but it's not always easy to retrofit that into an existing design. So I think several places in LLDB could be cleaned up by replacing some void* with Any. Note those places still exist today, so Any could be useful there right now.

Would it be expensive to reimplement those APIs using stateful functors while providing the backwards compatibility with something like:

void register(void (*f)(void*), void *v) { register([f, v] { f(v); }); }

Or is there something in the implementation (rather than the existing usage) that makes migrating difficult?

More recently while designing a basic API for a tracing library, I've gravitated towards a design where the library generates events (represented by some structure) depending on what happens. But since tracing is heavily platform dependent, there's only so much of this that you can abstract away into a common structure and there will be some additional pieces that only make sense on the particular platform. So you call:

unique_ptr<EventBase> Event = llvm::trace::WaitForEvent();
// Do something depending on what kind of event it is.
llvm::trace::ContinueFromEvent(std::move(Event));

In order for every possible platform to implement this correctly, there may need to be some platform-specific state maintained between the call to Wait and the call to Continue. Linux might need to pass the exact signal value and parameters. Windows might need to pass the exact EXCEPTION_RECORD structure that the OS filled out for us right before we return from WaitForEvent(). It's useful to be able to put that into an Any, so that we have something like:

class EventBase {
  TraceEventType Type;
  uint64_t ThreadId;
  uint64_t ProcessId;
  Any PlatformPiece;
};

class ThreadCreatedEvent : public EventBase {
  void *EntryPoint;
  void *TlsArea;
  // Etc.
};

class BreakpointEvent : public EventBase {
  uint64_t Address;
};

and so forth and so on. When creating this struct before returning from WaitForEvent(), the library can set PlatformPiece to some structure that would contain any platform-specific state needed to properly continue. I prefer not to use inheritance and llvm::cast here (e.g. WindowsBreakpointEvent, PosixBreakpointEvent, etc) because it just leads to inheritance hierarchy explosion and ultimately it's no more type-safe than any (someone still has to make an assumption about exactly what type it is).

If this is host-platform specific, why would this need to be runtime variable at all? Wouldn't the PlatformPiece be hardcoded to some specific layout based on the host platform the compiler was built for/running on?

Hopefully that all makes sense.

I think so - hopefully I'm replying in a way that does too.

Repeating my previous reply since it was through email and didn't make it onto Phab.

I've wanted this for quite a long time. My original motivation for this was back when i was working primarily in LLDB. There's a lot of places where void* is passed around as a generic parameter to a user callback. So, for example, you say DoSomethingAsynchronously(Callback, Param) and when the thing is finished, it invokes Callback(Param). There are obviously better ways to do this if you're designing this from the beginning, but it's not always easy to retrofit that into an existing design. So I think several places in LLDB could be cleaned up by replacing some void* with Any. Note those places still exist today, so Any could be useful there right now.

Would it be expensive to reimplement those APIs using stateful functors while providing the backwards compatibility with something like:

void register(void (*f)(void*), void *v) { register([f, v] { f(v); }); }

Or is there something in the implementation (rather than the existing usage) that makes migrating difficult?

It’s been a while since I was close to any of this code so it’s hard to say

More recently while designing a basic API for a tracing library, I've gravitated towards a design where the library generates events (represented by some structure) depending on what happens. But since tracing is heavily platform dependent, there's only so much of this that you can abstract away into a common structure and there will be some additional pieces that only make sense on the particular platform. So you call:

unique_ptr<EventBase> Event = llvm::trace::WaitForEvent();
// Do something depending on what kind of event it is.
llvm::trace::ContinueFromEvent(std::move(Event));

In order for every possible platform to implement this correctly, there may need to be some platform-specific state maintained between the call to Wait and the call to Continue. Linux might need to pass the exact signal value and parameters. Windows might need to pass the exact EXCEPTION_RECORD structure that the OS filled out for us right before we return from WaitForEvent(). It's useful to be able to put that into an Any, so that we have something like:

class EventBase {
  TraceEventType Type;
  uint64_t ThreadId;
  uint64_t ProcessId;
  Any PlatformPiece;
};

class ThreadCreatedEvent : public EventBase {
  void *EntryPoint;
  void *TlsArea;
  // Etc.
};

class BreakpointEvent : public EventBase {
  uint64_t Address;
};

and so forth and so on. When creating this struct before returning from WaitForEvent(), the library can set PlatformPiece to some structure that would contain any platform-specific state needed to properly continue. I prefer not to use inheritance and llvm::cast here (e.g. WindowsBreakpointEvent, PosixBreakpointEvent, etc) because it just leads to inheritance hierarchy explosion and ultimately it's no more type-safe than any (someone still has to make an assumption about exactly what type it is).

If this is host-platform specific, why would this need to be runtime variable at all? Wouldn't the PlatformPiece be hardcoded to some specific layout based on the host platform the compiler was built for/running on?

Not necessarily. For example you may have an x64 build of llvm tracing a 32 bit process. This can arise on Windows (wow64) as well as MachO (universal binaries). Maybe other platforms as well. You could probably narrow it to a variant, but we don’t have variant in llvm either and it’s much harder to implement and im not sure it buys much.

Also, in a system where events are passed around and potentially held onto by user code for a long time, it’s useful to have a generic “Tag” field that the user can attach arbitrary data to. This is another potential use case which I didn’t mention before.

these 2-3 use cases aside, given that it’s in c++17, seems like filling as many of the missing gaps as possible between our support libraries and c++17 can only be a good thing

Also, one more area where Any makes code cleaner with my planned design is that there isn't really a 1-to-1 mapping between platform events and library events, because platforms have such different semantics.

For example, in Windows a single platform events corresponds to what on Linux is covered by the union of SIGTRAP, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGUSR, and other various signals. And those signals themselves are Linux specific, and may be different from how the same types of events are communicated on Mac, BSD, or some other platform.

To abstract all these platform differences away, it's nice for the library's view of events to be independent of any particular platform. You might have a BreakpointEvent, an ArithmeticErrorEvent, an IllegalInstructionEvent, an InvalidMemoryReferenceEvent, and so on and so forth. On Windows, however, the PlatformPiece would be of the exact same concrete type for all of these (and others). So if you had some Windows specific code, you'd end up doing something like:

unique_ptr<EventBase> E = waitForEvent();
EXCEPTION_RECORD *E;
switch (E->Type) {
case EventBase::Breakpoint:
  E = &(dyn_cast<BreakpointEvent>(E.get())->PlatformPiece);
  break;
case EventBase::ArithmeticError:
  E = &(dyn_cast<ArithmeticErrorEvent>(E.get())->PlatformPiece);
  break;
case EventBase::IllegalInstruction:
  E = &(dyn_cast<IllegalInstructionEvent>(E.get())->PlatformPiece);
  break;
case EventBase::InvalidMemoryReference:
  E = &(dyn_cast<InvalidMemoryReferenceEvent>(E.get())->PlatformPiece);
  break;
}

On the other hand, with Any you can store the PlatformPiece directly in EventBase, and this code becomes:

unique_ptr<EventBase> E = waitForEvent();
const EXCEPTION_RECORD *E;
switch (E->Type) {
case EventBase::Breakpoint:
case EventBase::ArithmeticError:
case EventBase::IllegalInstruction:
case EventBase::InvalidMemoryReference:
  E = llvm::any_cast<const EXCEPTION_RECORD*>(&E->PlatformPiece);
  break;
}

Also, one more area where Any makes code cleaner with my planned design is that there isn't really a 1-to-1 mapping between platform events and library events, because platforms have such different semantics.

For example, in Windows a single platform events corresponds to what on Linux is covered by the union of SIGTRAP, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGUSR, and other various signals. And those signals themselves are Linux specific, and may be different from how the same types of events are communicated on Mac, BSD, or some other platform.

To abstract all these platform differences away, it's nice for the library's view of events to be independent of any particular platform. You might have a BreakpointEvent, an ArithmeticErrorEvent, an IllegalInstructionEvent, an InvalidMemoryReferenceEvent, and so on and so forth. On Windows, however, the PlatformPiece would be of the exact same concrete type for all of these (and others). So if you had some Windows specific code, you'd end up doing something like:

unique_ptr<EventBase> E = waitForEvent();
EXCEPTION_RECORD *E;
switch (E->Type) {
case EventBase::Breakpoint:
  E = &(dyn_cast<BreakpointEvent>(E.get())->PlatformPiece);
  break;
case EventBase::ArithmeticError:
  E = &(dyn_cast<ArithmeticErrorEvent>(E.get())->PlatformPiece);
  break;
case EventBase::IllegalInstruction:
  E = &(dyn_cast<IllegalInstructionEvent>(E.get())->PlatformPiece);
  break;
case EventBase::InvalidMemoryReference:
  E = &(dyn_cast<InvalidMemoryReferenceEvent>(E.get())->PlatformPiece);
  break;
}

On the other hand, with Any you can store the PlatformPiece directly in EventBase, and this code becomes:

unique_ptr<EventBase> E = waitForEvent();
const EXCEPTION_RECORD *E;
switch (E->Type) {
case EventBase::Breakpoint:
case EventBase::ArithmeticError:
case EventBase::IllegalInstruction:
case EventBase::InvalidMemoryReference:
  E = llvm::any_cast<const EXCEPTION_RECORD*>(&E->PlatformPiece);
  break;
}

Aren't virtual functions exactly for homogeneous operations over sub-classes?

unique_ptr<EventBase> Event = waitForEvent();
const EXCEPTION_RECORD *E;
switch (Event->Type) {
case EventBase::Breakpoint:
case EventBase::ArithmeticError:
case EventBase::IllegalInstruction:
case EventBase::InvalidMemoryReference:
  E = Event->getPlatformPiece();  // getPlatformPiece() is a virtual function in EventBase.
  break;
}

Aren't virtual functions exactly for homogeneous operations over sub-classes?

unique_ptr<EventBase> Event = waitForEvent();
const EXCEPTION_RECORD *E;
switch (Event->Type) {
case EventBase::Breakpoint:
case EventBase::ArithmeticError:
case EventBase::IllegalInstruction:
case EventBase::InvalidMemoryReference:
  E = Event->getPlatformPiece();  // getPlatformPiece() is a virtual function in EventBase.
  break;
}

And what type does it return?

Aren't virtual functions exactly for homogeneous operations over sub-classes?

unique_ptr<EventBase> Event = waitForEvent();
const EXCEPTION_RECORD *E;
switch (Event->Type) {
case EventBase::Breakpoint:
case EventBase::ArithmeticError:
case EventBase::IllegalInstruction:
case EventBase::InvalidMemoryReference:
  E = Event->getPlatformPiece();  // getPlatformPiece() is a virtual function in EventBase.
  break;
}

And what type does it return?

To elaborate, EXCEPTION_RECORD here is a platform specific type. It will fail to compile on any platform except Windows. So we can't expose that through a platform-agnostic interface. We could wrap it in a base class whose sole purpose is to hide this, such as:

struct PlatformPieceBase {
  virtual ~PlatformPieceBase() {}
};

struct Win32ExceptionRecordPlatformPiece : public PlatformPiece {
  EXCEPTION_RECORD E;
};

and then re-write the line in the switch as:

E = llvm::cast<Win32ExceptionRecordPlatformPiece>(Event->getPlatformPiece())->E;

But now you need to build an entire inheritance hierarchy just to wrap one structure which you already have an instance of from the OS. It seems like a lot of trouble to avoid using Any, and the "building an inheritance hierarchy to wrap..." is ultimately exactly what the implementation of Any does anyway.

To re-iterate a couple of other points mentioned earlier:

  1. For a given event type and host platform there would usually (but not always) be one type per platform. The not always is important, because it means a runtime discovery process is necessary in some cases.
  1. There are multiple event types, so if you did have a virtual function accessible from the base class, it would need to decide on a single common type to return. However, since there isn't a common type, you're ultimately just re-inventing Any without using Any by artificially injecting a base class that doesn't really do anything except satisfy the type system.
  1. Any is in C++17, which LLVM will eventually adopt

Ping. Anyone have any comments on the implementation?

Repeating my previous reply since it was through email and didn't make it onto Phab.

I've wanted this for quite a long time. My original motivation for this was back when i was working primarily in LLDB. There's a lot of places where void* is passed around as a generic parameter to a user callback. So, for example, you say DoSomethingAsynchronously(Callback, Param) and when the thing is finished, it invokes Callback(Param). There are obviously better ways to do this if you're designing this from the beginning, but it's not always easy to retrofit that into an existing design. So I think several places in LLDB could be cleaned up by replacing some void* with Any. Note those places still exist today, so Any could be useful there right now.

Would it be expensive to reimplement those APIs using stateful functors while providing the backwards compatibility with something like:

void register(void (*f)(void*), void *v) { register([f, v] { f(v); }); }

Or is there something in the implementation (rather than the existing usage) that makes migrating difficult?

It’s been a while since I was close to any of this code so it’s hard to say

More recently while designing a basic API for a tracing library, I've gravitated towards a design where the library generates events (represented by some structure) depending on what happens. But since tracing is heavily platform dependent, there's only so much of this that you can abstract away into a common structure and there will be some additional pieces that only make sense on the particular platform. So you call:

unique_ptr<EventBase> Event = llvm::trace::WaitForEvent();
// Do something depending on what kind of event it is.
llvm::trace::ContinueFromEvent(std::move(Event));

In order for every possible platform to implement this correctly, there may need to be some platform-specific state maintained between the call to Wait and the call to Continue. Linux might need to pass the exact signal value and parameters. Windows might need to pass the exact EXCEPTION_RECORD structure that the OS filled out for us right before we return from WaitForEvent(). It's useful to be able to put that into an Any, so that we have something like:

class EventBase {
  TraceEventType Type;
  uint64_t ThreadId;
  uint64_t ProcessId;
  Any PlatformPiece;
};

class ThreadCreatedEvent : public EventBase {
  void *EntryPoint;
  void *TlsArea;
  // Etc.
};

class BreakpointEvent : public EventBase {
  uint64_t Address;
};

and so forth and so on. When creating this struct before returning from WaitForEvent(), the library can set PlatformPiece to some structure that would contain any platform-specific state needed to properly continue. I prefer not to use inheritance and llvm::cast here (e.g. WindowsBreakpointEvent, PosixBreakpointEvent, etc) because it just leads to inheritance hierarchy explosion and ultimately it's no more type-safe than any (someone still has to make an assumption about exactly what type it is).

If this is host-platform specific, why would this need to be runtime variable at all? Wouldn't the PlatformPiece be hardcoded to some specific layout based on the host platform the compiler was built for/running on?

Not necessarily. For example you may have an x64 build of llvm tracing a 32 bit process.

Ah, good point.

This can arise on Windows (wow64) as well as MachO (universal binaries). Maybe other platforms as well. You could probably narrow it to a variant, but we don’t have variant in llvm either and it’s much harder to implement and im not sure it buys much.

I'd say variant buys a fair bit in terms of type safety/self-documenting code. I worry about both variant and any in terms of what they do to type safety/contracts between different pieces of code, hence my pushback on adding these kinds of constructs to LLVM.

Also, in a system where events are passed around and potentially held onto by user code for a long time, it’s useful to have a generic “Tag” field that the user can attach arbitrary data to. This is another potential use case which I didn’t mention before.

I'm still having a little trouble understanding what code is going to consume these events (& how's it going to know/support the different kinds of things in them) given how arbitrary (no common interface, etc) they may be?

these 2-3 use cases aside, given that it’s in c++17, seems like filling as many of the missing gaps as possible between our support libraries and c++17 can only be a good thing

If we were working with C++17 I'd push back in the same way against adding usage of these types to LLVM code for the same concerns (type safety, self documenting code, etc). Much like LLVM not using exceptions, iostreams, etc - not everything in the language is suitable for every codebase.

llvm/include/llvm/ADT/Any.h
25 ↗(On Diff #153639)

I think an out of line definition of Id is necessary here, maybe?

(also you can potentially do static const void *Id; + const void *TypeId::Id = &TypeId::Id; basically)

& I'd probably make TypeId a private implementation detail of 'Any'?

29 ↗(On Diff #153639)
= default
30 ↗(On Diff #153639)

Could probably add a cloneable CRTP helper some day - no doubt there are other hierarchies that could use that.

34–35 ↗(On Diff #153639)

Feels like you maybe might as well use the 'struct' keyword here too - since everything's public, except the deleted operators (which don't particularly benefit from being private anyway - so probably make them public too)

54 ↗(On Diff #153639)
= default
76–85 ↗(On Diff #153639)

You could collapse these into one operator that takes Any by value if you like.

Any &operator=(Any Other) {
  Storage = std::move(Other.Storage);
  return *this;
}
91–93 ↗(On Diff #153639)

Might as well match std::any and have these be called has_value() and reset()?

108 ↗(On Diff #153639)

Why add the 'const' here - if cv qualifiers are being removed anyway? Maybe I'm missing something about how std::decay/other parts of this are working?

(also std::decay says it removes cv qualifiers - so is it necessary to then explicitly remove them after that?)

114–135 ↗(On Diff #153639)

Implement these in terms of the pointer variants, perhaps? (to reduce duplication)

133–134 ↗(On Diff #153639)

Is this the right behavior according to the standard version of std::any? (should the passed in Any be left in the empty state, or in the non-empty state with a moved-from value? It looks like maybe the latter)

llvm/unittests/ADT/AnyTest.cpp
120–122 ↗(On Diff #153639)

Maybe create a named local variable for the pointer here - to demonstrate that this code is valid:

int *i = llvm::any_cast<int>(&A);
*i = 42;
...

The test as-is would pass even if any_cast returned a proxy object, which I would guess we don't want to allow (& instead want to allow/guarantee that users can write code that keeps pointers, etc).

This can arise on Windows (wow64) as well as MachO (universal binaries). Maybe other platforms as well. You could probably narrow it to a variant, but we don’t have variant in llvm either and it’s much harder to implement and im not sure it buys much.

I'd say variant buys a fair bit in terms of type safety/self-documenting code. I worry about both variant and any in terms of what they do to type safety/contracts between different pieces of code, hence my pushback on adding these kinds of constructs to LLVM.

Also, in a system where events are passed around and potentially held onto by user code for a long time, it’s useful to have a generic “Tag” field that the user can attach arbitrary data to. This is another potential use case which I didn’t mention before.

I'm still having a little trouble understanding what code is going to consume these events (& how's it going to know/support the different kinds of things in them) given how arbitrary (no common interface, etc) they may be?

There are certain things that are common to all platforms, and certain things that aren't. For example, if your process hits a trap instruction like an int 3, then no matter what platform you're on, there's going to be an address at which it occurred, a process id and thread id in the tracee, and a register context. On Windows, you'll have an EXCEPTION_DEBUG_INFO structure (https://msdn.microsoft.com/en-us/library/windows/desktop/ms679326(v=vs.85).aspx) which contains various other OS-specific bits of information that could be useful to platform specific code. I'm not sure what other things you might have on Linux, OSX, or BSD, but it's safe to assume there's something. If not specifically for a trap, for some kind of event (For example, when you call ptrace and it returns, it might have returned for many different reasons, and each reason means different info is available to query).

So the point of all this is: When a platform-specific event occurs, there is platform specific data associated with that event, only some of which can be factored out into a common interface, but all of which can be useful in certain scenarios.

So we give back the user an event object that has as wide of a common interface as possible, but still doesn't prevent access to all of the bits. The consumer of the common interface will be common code. Code that wants to decide what to do after hitting a breakpoint probably doesn't need all this platform specific information. It can just stop, look up line and symbol information based on the address, and report it to the user. But in a system like this which has deep hooks into the OS, it will be very frequent that the code handling an event needs to do something completely different depending on the platform. So we branch out into platform specific code, and in that platform specific code we know how to consume the platform specific event data.

So we give back the user an event object that has as wide of a common interface as possible, but still doesn't prevent access to all of the bits. The consumer of the common interface will be common code. Code that wants to decide what to do after hitting a breakpoint probably doesn't need all this platform specific information. It can just stop, look up line and symbol information based on the address, and report it to the user. But in a system like this which has deep hooks into the OS, it will be very frequent that the code handling an event needs to do something completely different depending on the platform. So we branch out into platform specific code, and in that platform specific code we know how to consume the platform specific event data.

Sounds pretty much the same as what LLVM's codebase would use polymorphism for (& dyn_cast, dyn_cast if chains, or switch over the type+cast, etc) since its inception. Some common API & some specific stuff that certain consumers care about. Doesn't necessarily mean it's the right way to do it - but there's certainly a lot of precedent (admittedly implemented before something like Any was as commonly thought about in C++, maybe).

Wouldn't mind hearing some other perspectives in here - I'm still a bit reticent, but happy enough to sign off (once the technical feedback is addressed) if you're really feeling it.

So we give back the user an event object that has as wide of a common interface as possible, but still doesn't prevent access to all of the bits. The consumer of the common interface will be common code. Code that wants to decide what to do after hitting a breakpoint probably doesn't need all this platform specific information. It can just stop, look up line and symbol information based on the address, and report it to the user. But in a system like this which has deep hooks into the OS, it will be very frequent that the code handling an event needs to do something completely different depending on the platform. So we branch out into platform specific code, and in that platform specific code we know how to consume the platform specific event data.

Sounds pretty much the same as what LLVM's codebase would use polymorphism for (& dyn_cast, dyn_cast if chains, or switch over the type+cast, etc) since its inception. Some common API & some specific stuff that certain consumers care about. Doesn't necessarily mean it's the right way to do it - but there's certainly a lot of precedent (admittedly implemented before something like Any was as commonly thought about in C++, maybe).

Wouldn't mind hearing some other perspectives in here - I'm still a bit reticent, but happy enough to sign off (once the technical feedback is addressed) if you're really feeling it.

Yes, the dyn_cast stuff is a good analogy. And it would be theoretically possible to do what I need with dyn_cast and a base class. The reason I'm hesitant to go that route though and prefer any over this approach is because it would really explode the inheritance hierarchy. For every event type you would need at least 1 subclass per platform. Just for a simple trap event, you'd need LinuxTrapEvent, BSDTrapEvent, Win32TrapEvent, Win64TrapEvent, WinWow6432TrapEvent. And ultimately each of these would just would probably just contain 1 or 2 trivially copyable fields that you got directly from an OS syscall. So the only purpose of the subclass would be to wrap some structure, not really to provide any actual functionality. At that point you're already skirting pretty close to Any anyway

llvm/include/llvm/ADT/Any.h
108 ↗(On Diff #153639)

I need to think about this some. I "borrowed" this line from MSVC's implementation of std::any, and without it I was getting mysterious errors. But it wouldn't hurt for me to understand exactly why.

zturner updated this revision to Diff 155262.Jul 12 2018, 1:39 PM

Updated version based on several suggestions from @dblaikie

dblaikie accepted this revision.Jul 16 2018, 11:36 AM
dblaikie added inline comments.
llvm/unittests/ADT/AnyTest.cpp
114–115 ↗(On Diff #155262)

Is there a test (didn't spot it) that demonstrates that the result of llvm::any_cast on an rvalue Any is the value moved-from the the Any, and that the Any contains the original move-from?

This revision is now accepted and ready to land.Jul 16 2018, 11:36 AM
zturner updated this revision to Diff 155742.Jul 16 2018, 12:51 PM
zturner added a subscriber: rsmith.

Added a test suggested by @dblaikie. Also reworked the enable_if condition on the copy-constructor based on comments from @rsmith that the previous check was incorrect for reasons that are still beyond my comprehension. In doing this, I had to add conjunction and negate -- which are part of C++17 -- to STLExtras.

llvm/unittests/ADT/AnyTest.cpp
114–115 ↗(On Diff #155262)

Is the newly added test in line with what you had in mind?

This revision was automatically updated to reflect the committed changes.
dblaikie added inline comments.Jul 23 2018, 6:19 PM
llvm/unittests/ADT/AnyTest.cpp
114–115 ↗(On Diff #155262)

Yeah, looks good!