简体   繁体   中英

std::map of template member function pointers

I was reading the answer to this question , and triying to figure out how to store in a std::map a template member function pointer, c++11.

class A {
  template<typename T>
  using MFP  = T (A::*)();

  std::map <string, MFP> fmap;

  template<typename T>
  T f() { return 1; }

  template<typename T>
  T g() { return 1.0f; }

  A() {
    fmap.insert(std::make_pair( "f", &A::f));
    fmap.insert(std::make_pair( "g", &A::g));
  }

  template<typename T>
  T Call(const string & s) {
    MFP fp = fmap[s];
    return (this->*fp)();
  }
};

The type alias compiles but when i use it in the std::map declaration i get the following error:

error: type/value mismatch at argument 2 in template parameter list for ‘template<class _Key, class _Tp, class _Compare, class _Alloc> class std::map’
 std::map<std::string, MFP> fmap;

Any ideas?

As Sam noticed, MFP is a template, while the second template argument of std::map requires a type. Thus you need to obtain actual types before populating the map with function pointers. Let me propose the most straightforward approach for this case - template class. With it you will need to list all desired return types on object instantiation, but the you will be able to Call with any of that types. I'll use std::function s instead of pointers, but you can easily roll back to function pointers.

First, we don't know how many types the class user will need, so let's make it variadic. Since map needs a complete type, we'll need a bunch of maps - one per type. The most common way do get it is a tuple, which needs pack expansion in our case. With the tuple we can search needed map in compile time and then search a function in it by name in runtime. Take a look at the code with explanations:

template<typename ...Types>
class B {
private:

  // Template alias for std::function.
  template<typename T>
  using MFP = std::function<T()>;

  /* Tuple of maps from std::string to MFP for all types
     in Types parameter pack. */
  std::tuple<std::map<std::string, MFP<Types>>...> fmap;

  template<typename T>
  T f() { return 2.5; }

  template<typename T>
  T g() { return 1.0f; }

  // Call implementation with compile-time pattern matching.
  // T is return type, U is current matching type
  template<typename T, size_t idx, typename U, typename ...Ts>
  struct CallImpl {
    static T callImpl(B* this_ptr, const std::string & s) {
      /* If we exhausted Ts pack, we have no proper instance for
         requested return type. Let's print a human-readable
         compilation error message. */
      static_assert((sizeof ... (Ts)) > 0,
        "Requested return type not found.");
      /* Otherwise discard U, increment tuple index
         and try the next type. */
      return CallImpl<T, idx + 1, Ts...>::callImpl(this_ptr, s);
    }
  };

  /* This partial specialization is called when return
   * type (T in above declaration) matches
   * stored type (U in above declaration). */
  template<typename T, size_t idx, typename ...Ts>
  struct CallImpl<T, idx, T, Ts...> {
    static T callImpl(B* this_ptr, const std::string & s) {
      /* First, get the map from tuple by index.
         This operation is either always valid in runtime or does not compile.
         Next, get function object from map. It may fail in runtime
         if user passed invalid string, so consider using map::at
         or add any other sensible logic for this case. */
      return std::get<idx>(this_ptr->fmap)[s]();
    }
  };

public:

  B() {
    /* Populate map with objects. Ellipsis in the last line
       expands Types as needed. */
    fmap = std::make_tuple(std::map<std::string, MFP<Types>>{
      {"f", std::bind(std::mem_fn(&B::f<Types>), this)},
      {"g", std::bind(std::mem_fn(&B::g<Types>), this)}
    }...);
  }

  template<typename T>
  T Call(const std::string & s) {
    /* Start pattern matching with zero index. */
    return CallImpl<T, 0, Types...>::callImpl(this, s);
  }
};

Usage:

int main() {

    B<int, float, short> a; // Provides int, float and short return types.
    std::cout << a.Call<int>("f") << std::endl; // Prints 2, which is 2.5 casted to int.
    std::cout << a.Call<float>("f") << std::endl; // Prints 2.5

    // Compilation error with "Requested type not found." message among others.
    std::cout << a.Call<double>("f") << std::endl; 
}

Some notes:

  • 2.5 in f declaration is double literal, but double is not listed in B<int, float> a; , this we get compilation error on a.Call<double>("whatever") .
  • Code of Call method and callImpl function for short is not generated at all, as we do not instantiate it.

The second template parameter to a std::map is a class, or a type, of the map's value. For example:

std::map<std::string, int> mapsi;

The second template parameter is the type int , an integer.

Your declaration:

std::map <string, MFP> fmap;

MFP is not a class, or a type. MFP is another template. This is equivalent to writing:

std::map <string, template<typename T> T (A::*)()> fmap;

Which makes no sense. Hence the compilation error.

You could write:

std::map <string, MFP<int>> fmap;

or

std::map <string, MFP<std::string>> fmap;

MVP<int> and MFP<std::string> are actual types, actual classes, hence you can put them into a map. You can't put a template into a map, because a template is not an actual class, or a type.

template<typename T>
T f() { return 1; }

template<typename T>
T g() { return 1.0f; }

You apparently believe that these are member functions. They're not. They're member templates. There's no such thing as a pointer to a member template, only a pointer to a member function.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM