10

Let's say I have a std::any object which may or may not contain a pointer to some derived class of a given base class B. Is there any way I can do something that:

  1. Returns a B *, if the std::any object holds something convertible to B *, or
  2. Throws an exception if not?

It seems like dynamic_cast and std::any_cast each provide one half of this functionality, but I don't see any way of putting the two together.

I'm aware that I can make this work in various ways that involve explicitly enumerating every type convertible to B *, but that's the mother of all DRY violations.


Sample use case:

std::vector<std::any> setupTools(const std::string & confFile)
{
  std::vector<std::any> myTools;

  auto conf = parse(confFile);

  for(std::string & wrenchInfo : conf["Wrenches"])
  {
    Wrench::setup(myTools, wrenchInfo);
  }    

  for(std::string & hammerInfo : conf["Hammers"])
  {
    Hammer::setup(myTools, hammerInfo);
  }

   // 25 more kinds of tools
}

Factory1::init(const std::vector<std::any> & tools)
{
  m_wrench = any_get<Wrench *>(tools);
  m_hammer = any_get<Hammer *>(tools);
  m_saw = any_get<Saw *>(tools);
}

Factory2::init(const std::vector<std::any> & tools)
{
  m_wrench = any_get<TorqueWrench *>(tools);
}

Factory3::init(const std::vector<std::any> & tools)
{
  m_saw = any_get<CordlessReciprocatingSaw *>(tools);
}

I don't want to include a bunch of boilerplate code listing every single kind of saw in existence just so I can grab a saw -- any Saw -- to use in Factory1.

Daniel McLaury
  • 4,047
  • 1
  • 15
  • 37
  • 1
    Can you give some explanation as to why you need to use `std::any` for this, and what you're trying to solve overall? This sounds like a very awkward problem and there's probably a better way around it – alter_igel Nov 05 '19 at 19:29
  • 3
    At risk of stating the obvious, if you agree to only put `B*`s into the `std::any` object, and not derived class pointers, then that solves the problem quite easily. – Brian Bi Nov 05 '19 at 19:30
  • @Brian: Doesn't fit my use case. – Daniel McLaury Nov 05 '19 at 19:31
  • 2
    If it doesn't fit your use case then can you explain your use case? – Alan Birtles Nov 05 '19 at 19:33
  • Added sample use-case. – Daniel McLaury Nov 05 '19 at 19:47
  • 1
    If all of `myTools` are tools why not just have a `Tool` base class and make `myTools` a `std::vector>`? – Alan Birtles Nov 05 '19 at 19:51
  • @AlanBirtles: That may be an option. I'd have to go back and add a useless base-class to everything -- there is no overlapping functionality between saws and hammers -- but it seems cleaner than anything else I've found. – Daniel McLaury Nov 05 '19 at 20:04
  • 1
    @DanielMcLaury: "*there is no overlapping functionality between saws and hammers*" Then why are they in the same array? There clearly is *some* logical commonality between them, because you seem to want to stick them in the same place. That is, I don't understand why `myTools` exists; why do you want to put what you claim are entirely dissimilar objects into it, then iterate over them to try to remember what you put there? – Nicol Bolas Nov 05 '19 at 20:08
  • @NicolBolas: I don't gain anything by putting them in separate collections, because if I want to grab a `CordlessReciprocatingSaw` then whether I'm iterating over all the tools or all the saws I'm still iterating over a bunch of stuff that is not `CordlessReciprocatingSaw`s. – Daniel McLaury Nov 05 '19 at 20:54
  • Is there no baseclass for all tools? – Deduplicator Nov 05 '19 at 22:29

3 Answers3

6

This is unachievable. It is only possible to get an object out from std::any using exactly the type that was put inside. Thus, you must know the type to get anything out of it.

It seems that std::any does not fit your use case.

eerorika
  • 232,697
  • 12
  • 197
  • 326
2

I'm late to the party, just just came across this question looking for an answer myself.

Here's what I eventually worked out.

I needed to pass either a wxMenuItem*, or a pointer to some sort of control (all are derived from wxControl) to my function in an std::any. I know if I have a wxMenuItem* or a control based on flags. So ignore wxMenuItem* 's for now.

As my code only has to work with what a base class wxControl has, (and I imagine since your hammers share a common base class, you only want to do base hammer type stuff), I...

static_cast<wxControl*>(myCombo*)

at the calling point, and

auto myPtr {std::any_cast<wxControl*>(theAnyField)};

at the called point, and voila.

  • I imagine this has some downsides for certain use cases, but this is perfect for mine too. And, if you need to get the original type, just dynamic_cast the result of any_cast! – golvok Dec 31 '20 at 06:02
0

Adding a base class "Tool" that has an enum ToolType attribute can help out. Then using the ToolType you can decide on the cast.

Pedro Ferreira
  • 852
  • 9
  • 23