问题
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;
回答1:
What you need is the following type transformation.
- given
T
, returnT
- given
T *
, returnT
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>
, returnsmart_ptr<T>::element_type
, which should beT
- given a pointer type
T *
, returnT
- otherwise, given
T
, returnT
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 nestedtypedef
namedelement_type
, returnvoid
- 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);
}
来源:https://stackoverflow.com/questions/34690980/computing-the-type-of-a-function-pointer