1

Consider the following:

template<typename T>
struct S
{
    typedef M< &T::foo > MT;
}

This would work for:

S<Widget> SW;

where Widget::foo() is some function

How would I modify the definition of struct S to allow the following instead:

S<Widget*> SWP;
unshul
  • 269
  • 3
  • 16
  • 2
    Should it work for *only* pointers or for pointers and non-pointers alike? – 5gon12eder Jan 09 '16 at 07:44
  • 3
    Some things in the question are unclear: what is `M`? How is `Widget::foo` connected to the type that you instantiate? What are you trying to accomplish? – Chris Beck Jan 09 '16 at 07:45
  • Ideally it should work for both pointers and non-pointers, however I would really even appreciate a version of the struct that worked with pointer types alone. – unshul Jan 09 '16 at 07:47
  • @ChrisBeck: What I am really trying to accomplish: http://stackoverflow.com/questions/34688926/c-determine-the-class-of-a-pointer-type-in-the-use-of-boost-multiindex – unshul Jan 09 '16 at 07:48
  • What on earth is `&T::()`? – T.C. Jan 09 '16 at 07:49
  • @T.C. : Typo, please see corrected version. Thanks – unshul Jan 09 '16 at 07:51
  • Now I don't understand what you're trying to do anymore at all. I thought you wanted a `typedef` to a *pointer to member [function]* of the type `S` is instantiated with. Is this so? How does this relate with your other question? Can you maybe show how you *want to use* the construct? – 5gon12eder Jan 09 '16 at 07:52
  • When asked what you want to do, you link to another question, where there has been a request for clarification that is 5 hours old left unsatisfied. So is what you want to do is "ask unclear questions and refuse to clarify"? – Yakk - Adam Nevraumont Jan 09 '16 at 07:59
  • @5gon12eder: The example in the link below may be clearer. Instead of "employee" I want to use some type T which may be a pointer to some other class. http://www.boost.org/doc/libs/1_60_0/libs/multi_index/doc/tutorial/key_extraction.html#const_mem_fun – unshul Jan 09 '16 at 07:59
  • So the Boost example should also work with a container of `employee *`s? Is that what you want? – 5gon12eder Jan 09 '16 at 08:03
  • @Yakk: Fair point. I was trying to distill my original request down to something much simpler here. – unshul Jan 09 '16 at 08:04
  • @5gon12eder : Yes. However, I want to say multi_index_container< T, ... > ... if T is a de-referenceable type then I want to compute the type of *T to describe &()::foo – unshul Jan 09 '16 at 08:06
  • 1
    Thanks, I got it now; currently working on an answer. – 5gon12eder Jan 09 '16 at 08:11

1 Answers1

2

What you need is the following type transformation.

  • given T, return T
  • given T *, return T

It so happens that the standard library already has implemented this for us in std::remove_pointer (though it's not hard to do yourself).

With this, you can then write

using object_type = std::remove_pointer_t<T>;
using return_type = /* whatever foo returns */;
using MT = M<object_type, return_type, &object_type::foo>;

Regarding your comment that you also want to work with smart pointers, we have to re-define the type transformation.

  • given a smart pointer type smart_ptr<T>, return smart_ptr<T>::element_type, which should be T
  • given a pointer type T *, return T
  • otherwise, given T, return T itself

For this, we'll have to code our own meta-function. At least, I'm not aware of anything in the standard library that would help here.

We start by defining the primary template (the “otherwise” case).

template <typename T, typename = void>
struct unwrap_obect_type { using type = T; };

The second (anonymous) type parameter that is defaulted to void will be of use later.

For (raw) pointers, we provide the following partial specialization.

template <typename T>
struct unwrap_obect_type<T *, void> { using type = T; };

If we'd stop here, we'd basically get std::remove_pointer. But we'll add an additional partial specialization for smart pointers. Of course, we'll first have to define what a “smart pointer” is. For the purpose of this example, we'll treat every type with a nested typedef named element_type as a smart pointer. Adjust this definition as you see fit.

template <typename T>
struct unwrap_obect_type
<
  T,
  std::conditional_t<false, typename T::element_type, void>
>
{
  using type = typename T::element_type;
};

The second type parameter std::conditional_t<false, typename T::element_type, void> is a convoluted way to simulate std::void_t in C++14. The idea is that we have the following partial type function.

  • given a type T with a nested typedef named element_type, return void
  • otherwise, trigger a substitution failure

Therefore, if we are dealing with a smart pointer, we'll get a better match than the primary template and otherwise, SFINAE will remove this partial specialization from further consideration.

Here is a working example. T.C. has suggested using std::mem_fn to invoke the member function. This makes the code a lot cleaner than my initial example.

#include <cstddef>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <utility>

template <typename ObjT, typename RetT, RetT (ObjT::*Pmf)() const noexcept>
struct M
{
  template <typename ThingT>
  static RetT
  call(ThingT&& thing) noexcept
  {
    auto wrapper = std::mem_fn(Pmf);
    return wrapper(std::forward<ThingT>(thing));
  }
};

template <typename T, typename = void>
struct unwrap_obect_type { using type = T; };

template <typename T>
struct unwrap_obect_type<T *, void> { using type = T; };

template <typename T>
struct unwrap_obect_type<T, std::conditional_t<false, typename T::element_type, void>> { using type = typename T::element_type; };

template <typename T>
struct S
{

  template <typename ThingT>
  void
  operator()(ThingT&& thing) const noexcept
  {
    using object_type = typename unwrap_obect_type<T>::type;
    using id_caller_type          = M<object_type, int,                &object_type::id>;
    using name_caller_type        = M<object_type, const std::string&, &object_type::name>;
    using name_length_caller_type = M<object_type, std::size_t,        &object_type::name_length>;
    std::cout << "id:          " << id_caller_type::call(thing)          << "\n";
    std::cout << "name:        " << name_caller_type::call(thing)        << "\n";
    std::cout << "name_length: " << name_length_caller_type::call(thing) << "\n";
  }

};

class employee final
{

 private:

  int id_ {};
  std::string name_ {};

 public:

  employee(int id, std::string name) : id_ {id}, name_ {std::move(name)}
  {
  }

  int                  id()          const noexcept { return this->id_; }
  const std::string&   name()        const noexcept { return this->name_; }
  std::size_t          name_length() const noexcept { return this->name_.length(); }

};

int
main()
{
  const auto bob = std::make_shared<employee>(100, "Smart Bob");
  const auto s_object = S<employee> {};
  const auto s_pointer = S<employee *> {};
  const auto s_smart_pointer = S<std::shared_ptr<employee>> {};
  s_object(*bob);
  std::cout << "\n";
  s_pointer(bob.get());
  std::cout << "\n";
  s_smart_pointer(bob);
}
5gon12eder
  • 24,280
  • 5
  • 45
  • 92
  • "Note that in order to actually invoke the member function, I had to introduce the helper that conditionally dereferences the pointer." We have `INVOKE` for that. – T.C. Jan 09 '16 at 09:59
  • @T.C. Well, we *will* have… But even then, will `std::invoke` automagically call `(o.*pmf)()` or `(p->*pmf)()` depending on whether the callable is a pointer? – 5gon12eder Jan 09 '16 at 10:07
  • 1
    We have it already (via `mem_fn`). And yes, it will handle dereferencing (it does `((*p).*pmf)()` so that it even works with smart pointers and isn't confused by `->*` overloads). Also, the callable here is the PMF, not the object/pointer. – T.C. Jan 09 '16 at 16:44
  • @5gon12eder thanks! I did see std:remove_pointer when I tried to solve this myself, however, I was under the impression that it does not work with smart pointer types. Is this true? – unshul Jan 09 '16 at 17:33
  • @T.C. I didn't know that this works / exists. I find it pretty awesome, thanks! I have updated the code. Of course you are also right that the callable is not the (pointer to the) object here. – 5gon12eder Jan 09 '16 at 22:42
  • 1
    @unshul It doesn't work with smart pointers and you didn't mention smart pointers in your OP. I'm also not sure you really want this because if you have a container of smart pointers, wouldn't it be more useful to invoke member functions (like `get`) on the smart pointers themselves? Anyway, I have updated the answer to account for smart pointers as well. – 5gon12eder Jan 09 '16 at 22:44
  • @5gon12eder I wanted to make this collection as close to STL containers as possible. Just as we can declare std::vector or std::vector>, I would like to do the same with my collection. Thanks for your effort in answering my question. – unshul Jan 09 '16 at 22:55