Getting type_info from variadic template breaks the compiler... why?

198 views Asked by At

So I am essentially trying to shove the rtti of a parameter pack into a list of type_info*. But for some reason, it doesn't seem to compile (or rather the compiler gives up half way through). Either way I can't seem to figure it out. Does anyone have a method to fix this, or better yet, know why it's breaking? Anywho, here's the code:

#pragma once
#include <typeinfo>
#include <vector>

class ParamChecker
{
public:
    typedef std::vector<const type_info*> Types;

    template <typename T> void PushType()
    {
        types.push_back(&typeid(T));
    }

    template <typename Head, typename... Tail> void PushTypes()
    {
        PushType<Head>();
        PushTypes<Tail...>();
    }
    void PushTypes() {}

private:
    Types types;
};

Thanks in advance!

1

There are 1 answers

6
mpark On BEST ANSWER

I don't have Visual Studio to test, but I see the problem in your code so I'll tell you about that, and you can test on Visual Studio.

The problem is that, when you recurse into PushTypes<Tail...>(); and Tail... is empty, you're calling, PushTypes<>();. Notice that your base case, void PushTypes() {} is not a template function, i.e. you can't call it via PushTypes<>();.

Also note that you'll need a class template as a helper because we don't have partial specializations for function templates yet (hopefully it'll be coming soon).

But here's what you can do.

#include <typeinfo>
#include <vector>

class ParamChecker {
  public:

  /* Our type info structure. */
  using Types = std::vector<const std::type_info *>;

  /* Delegate work to PushTypesImpl<>. */
  template <typename... Types>
  void PushTypes() {
    PushTypesImpl<Types...>()(types_);
  }

  private:

  /* Forward declaration. */
  template <typename... Types>
  class PushTypesImpl;

  /* Collection of type information. */
  Types types_;

};  // ParamChecker

/* Base case. */
template <>
class ParamChecker::PushTypesImpl<> {
  public:

  void operator()(Types &) const { /* Do nothing. */ }

};

/* Recursive case. */
template <typename Head, typename... Tail>
class ParamChecker::PushTypesImpl<Head, Tail...> {
  public:

  void operator()(Types &types) const {
    types.push_back(&typeid(Head));
    PushTypesImpl<Tail...>()(types);
  }

};

int main() {
  ParamChecker x;
  x.PushTypes<>();  // push nothing.
  x.PushTypes<int>();  // push int.
  x.PushTypes<int, double>();  // push int and double.
}

EDIT: The following is an alternative approach using type_list and passing it as an argument.

NOTE: The use of type_list<> here instead of tuple<> is because constructing an empty tuple requires that all of the types be default-constructable, and even if they were all default-constructable, we don't want to default-construct them for no reason.

template <typename... Types>
class type_list {};

class ParamChecker {
  public:

  /* Our type info structure. */
  using Types = std::vector<const std::type_info *>;

  /* Base case. Do nothing. */
  void PushTypes(type_list<> &&) {}

  /* Recursive case. */
  template <typename Head, typename... Tail>
  void PushTypes(type_list<Head, Tail...> &&) {
    types_.push_back(&typeid(Head));
    PushTypes(type_list<Tail...>());
  }

  private:

  /* Collection of type information. */
  Types types_;

};  // ParamChecker

int main() {
  ParamChecker x;
  x.PushTypes(type_list<>());  // push nothing.
  x.PushTypes(type_list<int>());  // push int.
  x.PushTypes(type_list<int, double>());  // push int and double.
}