Question to C++ template gurus:
I have created two template "policies" (not sure if this is the right term), which implement storage of some value types in a vector of either dumb or smart pointers:
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
template <typename T>
class DumbPtrVec
{
std::vector<T*> m_vec;
public:
using handle = size_t;
~DumbPtrVec() {
std::for_each(begin(m_vec), end(m_vec), [](T* p){ delete p; });
}
handle AddElement(T* p) {
const handle index = m_vec.size();
m_vec.push_back(p);
return index;
}
T* GetElement(const handle& i) {
T* p = (i < m_vec.size())? m_vec[i] : nullptr;
return p;
}
};
template <typename T>
class SmartPtrVec
{
std::vector<std::shared_ptr<T>> m_vec;
public:
using handle = std::weak_ptr<T>;
handle AddElement(T* p) {
m_vec.emplace_back(p);
return m_vec.back(); // gets converted to weak_ptr
}
T* GetElement(const handle& i) {
T* p = (i.expired())? nullptr : i.lock().get();
return p;
}
};
template <typename T, template<typename> typename STORAGE>
class Storage
{
STORAGE<T> m_values;
public:
using handle = typename STORAGE<int>::handle;
handle AddValue(T* v) { return m_values.AddElement(v); }
T* GetValue(handle h) { return m_values.GetElement(h); }
};
int main()
{
constexpr int N = 13;
Storage<int, DumbPtrVec> d;
auto dh = d.AddValue(new int(N));
std::cout << *d.GetValue(dh) << " == " << N <<std::endl;
Storage<int, SmartPtrVec> s;
auto sh = s.AddValue(new int(N));
std::cout << *s.GetValue(sh) << " == " << N << std::endl;
return 0;
}
Everything works fine, so far.
Then I added a template wrapper, that replaces the element "handle" with a unique string and keeps a look-up table for converting strings back to the handles. If this class is explicitly derived from either DumbPtrVec or SmartPtrVec class, everything works, e.g. for SmartPtrVec:
template <typename T>
class StringHandleWrapper : SmartPtrVec<T>
{
using super = typename SmartPtrVec<T>;
using Str2HandleMap = std::unordered_map<std::string, typename super::handle>;
Str2HandleMap m_Name2HandleMap;
public:
using handle = std::string;
handle AddElement(T* p) {
typename super::handle elem = super::AddElement(p);
static int counter = 0;
std::string uuid = std::to_string(++counter);
m_Name2HandleMap[uuid] = elem;
return uuid;
}
T* GetElement(const handle& uuid) {
auto it = m_Name2HandleMap.find(uuid);
return (it != m_Name2HandleMap.end())? super::GetElement(it->second) : nullptr;
}
};
and successful invocation:
Storage<int, StringHandleWrapper> s;
std::string handle = s.AddValue(new int(N));
But I can't figure out how to add a second template parameter, STORAGE, to StringHandleWrapper, so that it could wrap any of DumbPtrVec or SmartPtrVec...
If I change StringHandleWrapper to:
template <typename T, template<typename> typename STORAGE>
class StringHandleWrapper : STORAGE<T>
{
using super = typename STORAGE<T>;
//... rest unchanged
then I can't figure out how to instantiate Storage class, as compiler complains about "too few template arguments":
Storage<int, StringHandleWrapper<SmartPtrVec>> s;
I hope I'm missing something simple...
Thank you for taking your time to look through my long question!
Create another level of template for partial argument application:
template <template <typename, template<typename> typename> class W,
template <typename> typename S>
struct Apply
{
template <typename T> using type = W<T, S>;
};
Then instantiate Storage like this:
Storage<int, Apply<StringHandleWrapper, SmartPtrVec>::type> s;
Just found the answer (it was indeed simple):
I needed to introduce two single-parameter templates
template<typename T> using StringDumbHandleWrapper = StringHandleWrapper<T, DumbPtrVec>;
template<typename T> using StringSmartHandleWrapper = StringHandleWrapper<T, SmartPtrVec>;
and use the new names in Storage instantiation, e.g.
Storage<int, StringDumbHandleWrapper> s;
So much for the long question... :)
Related
I have a vector of pointers to Base.
Invariant: only one of each derived type should be in that vector at any time.
I also want to be able to lookup the value with a given type in O(1). I can do this in O(n) easily, by checking dynamic_cast.
Basically, I want to replace my vector with a map or something. Is that possible?
Here's minimal example with the vector and the loop:
#include <functional>
#include <iostream>
#include <memory>
#include <type_traits>
#include <vector>
using namespace std;
typedef struct Base {
virtual ~Base(){};
} Base;
vector<unique_ptr<Base>> baseList;
template <typename NarrowType,
typename std::enable_if_t<
! std::is_same_v<Base, NarrowType> &&
std::is_base_of_v<Base, NarrowType>,
bool> = true>
void ApplyFuncToType(function<void(NarrowType)> func) {
// Want to get rid of this loop
for (auto &base : baseList) {
NarrowType *narrow = dynamic_cast<NarrowType *>(base.get());
if (narrow) {
func(*narrow);
}
}
}
// usage
int main() {
typedef struct A : Base {
void printA() { cout << "a" << endl; }
} A;
typedef struct B : Base {
void printB() { cout << "b" << endl; }
} B;
baseList.push_back(make_unique<A>());
baseList.push_back(make_unique<B>());
ApplyFuncToType<A>([](A a) { a.printA(); });
}
Questions:
How can I enfore my invariant (one of each type max in container)
Would a unordered_map<type_info, unique_ptr<Base>> be a good solution to this? I have read that typeid is not consistent or safe to use or something, but am not sure exactly.
Edits/Clarification:
This is for a system where other classes can register their own types in this vector. i.e. the contents of the vector will change during runtime.
A similar approach is shown here, where an unordered_map is used to allow self-registered event callbacks.
Yeah, sure, it's possible, but I'm not convinced you need it. After all, all your types are completely static.
Also, ApplyFuncToType shouldn't be taking std::function, but a generic argument, since you'll save on the cost of shoehorning things into std::function. You're not deducing any types anyway - because std::function is not a tool for that - and thus you have the call that includes the type parameter explicitly: ApplyFuncToType<A>.
And finally, it's probably wrong to pass A and B to the lambda by value - since then the instance the lambda is using is not the instance you so carefully deposited beforehand (!). It should be passed by const reference, or by reference if it's a non-const method:
// Do this
ApplyFuncToType<A>([](const A &a) { a.printA(); });
// Or do that
ApplyFuncToType<A>([](A &a) { a.printA(); });
// NO!
ApplyFuncToType<A>([](A a) { a.printA(); });
It's hard to deduce it ahead of time, but I imagine that you'd want to make A, B, ... non-copyable but they definitely should be movable (read on).
A Tuple of Pointers
All you really want is the below - and it doesn't care that the types are derived from some base, you can use any types you wish. You can of course add type constraints if you want to protect from bugs where wrong types are supplied to ptr_tuple.
#include <functional>
#include <memory>
#include <tuple>
struct A { void methodA() {} };
struct B { void methodB() {} };
template <class ...Args>
using ptr_tuple = std::tuple<std::unique_ptr<Args>...>;
ptr_tuple<A, B> instances;
template <typename T>
auto &instance()
{
return std::get<std::unique_ptr<T>>(instances);
}
template <class T, class Fun, class ...Args>
void invoke(Fun &&fun, Args &&...args)
{
auto *ptr = instance<T>().get();
if (ptr) {
std::invoke(fun, *ptr, std::forward<Args>(args)...);
}
}
int main() {
instance<A>() = std::make_unique<A>();
instance<B>() = std::make_unique<B>();
invoke<A>([](A& a){ a.methodA(); });
invoke<B>([](B& b){ b.methodB(); });
}
Argument Deduction for Invoke/Apply
It's not even necessary to supply the explicit type parameter to invoke. We can deduce it. For that, we use a traits class that's sorely missing in C++ standard library:
// from https://stackoverflow.com/a/39717241/1329652
// see also
// https://github.com/kennytm/utils/blob/master/traits.hpp
// https://stackoverflow.com/a/27885283/1329652
// boost::callable_traits
template <typename T, typename = void>
struct function_traits;
template <typename R, typename... A>
struct function_traits<R (*)(A...)>
{
using args_type = std::tuple<A... >;
using arg0_class = std::decay_t<std::tuple_element_t<0, args_type>>;
};
template <typename R, typename C, typename... A>
struct function_traits<R (C::*)(A...)>
{
using args_type = std::tuple<A... >;
using arg0_class = std::decay_t<std::tuple_element_t<0, args_type>>;
};
template <typename R, typename C, typename... A>
struct function_traits<R (C::*)(A...) const>
{
using args_type = std::tuple<A... >;
using arg0_class = std::decay_t<std::tuple_element_t<0, args_type>>;
};
template <typename T>
struct function_traits<T, std::void_t<decltype(&T::operator())> >
: public function_traits< decltype(&T::operator()) >
{};
And then we can deduce the needed type in invoke:
template <class Fun, class ...Args>
void invoke(Fun &&fun, Args &&...args)
{
using arg0_class = typename function_traits<std::decay_t<Fun>>::arg0_class;
auto *ptr = instance<arg0_class>().get();
if (ptr) {
std::invoke(fun, *ptr, std::forward<Args>(args)...);
}
}
int main() {
instance<A>() = std::make_unique<A>();
instance<B>() = std::make_unique<B>();
invoke([](A& a){ a.methodA(); });
invoke([](B& b){ b.methodB(); });
}
A Tuple of Optional Values
Depending on what your A and B types really are, if they can be moved, then using dynamic memory allocation is totally unnecessary, you'd much rather keep them by value, e.g. with optional:
#include <functional>
#include <memory>
#include <optional>
#include <tuple>
struct A { void methodA() {} };
struct B { void methodB() {} };
template <class ...Args>
using opt_tuple = std::tuple<std::optional<Args>...>;
opt_tuple<A, B> instances;
template <typename T> auto &instance()
{
return std::get<std::optional<T>>(instances);
}
template <class T, class Fun, class ...Args>
void invoke(Fun &&fun, Args &&...args)
{
auto &opt = instance<T>();
if (opt) {
std::invoke(fun, *opt, std::forward<Args>(args)...);
}
}
int main() {
instance<A>().emplace(); // constructs A
instance<B>().emplace(); // constructs B
invoke<A>([](A& a){ a.methodA(); });
invoke<B>([](B& b){ b.methodB(); });
}
Of course you can add the type-deduced variant of invoke just as before.
A type-id Stand In
Even though I really think that your original solution is in want of a problem - you should state what problem you're trying to solve, otherwise it smells of an XY problem - there of course is a better "type id" than type_id: an address of a function templated on a type. There'll be only one instance of it per program.
I don't think that the "O(1)" lookup is a real requirement, a very, very fast O(log(N)) lookup - way faster than you'd get from e.g. std::map, will work just as well for whatever your imaginary applications is.
Thus:
#include <cassert>
#include <functional>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <vector>
// here goes function_traits implementation from above
struct Base {};
template <typename T>
constexpr static bool is_derived_from_Base_v =
!std::is_same_v<Base, T> && std::is_base_of_v<Base, T>;
class UniqueTypeObjects {
using marker_type = void(*)();
struct Pair {
std::unique_ptr<Base> base;
marker_type marker;
Pair(std::unique_ptr<Base> &&base, marker_type marker) : base(std::move(base)), marker(marker) {}
bool operator<(marker_type o) const { return marker < o; }
};
friend bool operator<(marker_type a, const Pair &o);
template <typename T, typename = std::enable_if<is_derived_from_Base_v<T>>>
struct Witness {
static void marker() {}
};
std::vector<Pair> m_objects;
public:
template <class Derived, class =
std::enable_if_t<is_derived_from_Base_v<Derived>>>
void insert(std::unique_ptr<Derived> &&obj) {
auto constexpr marker = &Witness<Derived>::marker;
auto it = std::lower_bound(m_objects.begin(), m_objects.end(), marker);
if (it != m_objects.end() && it->marker == marker)
throw std::logic_error("Attempting to insert an object of duplicate type");
m_objects.emplace(it, std::move(obj), marker);
}
template <typename Derived, typename Fun,
class = std::enable_if_t<is_derived_from_Base_v<Derived>>>
void apply(Fun fun) const {
auto constexpr marker = &Witness<Derived>::marker;
auto it = std::lower_bound(m_objects.begin(), m_objects.end(), marker);
if (it == m_objects.end() || it->marker != marker)
throw std::runtime_error("No object found to apply the function to");
std::invoke(fun, *static_cast<Derived*>(it->base.get()));
}
template <typename Fun,
class = std::enable_if_t<is_derived_from_Base_v<
typename function_traits<std::decay_t<Fun>>::arg0_class>>>
void apply(Fun fun) const {
using arg0_class = typename function_traits<std::decay_t<Fun>>::arg0_class;
apply<arg0_class>(std::move(fun));
}
};
bool operator<(void(*a)(), const UniqueTypeObjects::Pair &o)
{ return a < o.marker; }
char lastInvoked;
int main() {
struct A : Base {
void methodA() { lastInvoked = 'A'; }
};
struct B : Base {
void methodB() { lastInvoked = 'B'; }
};
UniqueTypeObjects uto;
uto.insert(std::make_unique<A>());
uto.insert(std::make_unique<B>());
assert(!lastInvoked);
uto.apply([](A &a){ a.methodA(); });
assert(lastInvoked == 'A');
uto.apply([](B &b){ b.methodB(); });
assert(lastInvoked == 'B');
}
But I still don't think it's necessary. If you truly have O(1) requirement, e.g. some sort of a realtime system, or system with deterministic execution timing, then the opt_tuple solution or its equivalent is the one you should use. Otherwise - good luck with the paperwork and test plans to ensure that UniqueTypeObjects works. I wrote the thing and even I wouldn't allow it in a realtime or hi-reliability codebase I maintained. Nothing beats static type safety and ensuring correctness by design, and you get that with the tuple approach (or its equivalent with a custom class).
I need to write a template in C++ that implements an abstract version of a set. I can't find a solution (or worse, don't really understand what to do) about a compile error.
This is a simplified version of the main program I need to compile and run -- that is, I MUST NOT change anything in this:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <numeric>
#include <set>
#include <string>
#include "testset.h"
using namespace std;
struct string_size_less
{
bool operator()( const std::string& a,
const std::string& b )
{
return a.size() < b.size();
}
};
int main()
{
std::set<std::string> msgs;
msgs.insert("One");
msgs.insert("Two");
msgs.insert("Three");
set_ops<std::string> ops(msgs);
ops.list();
std::set<std::string, string_size_less> x;
x.insert("Hello");
x.insert("Ciao");
std::set<std::string, std::greater<std::string> > a;
a.insert(":-o");
set_ops<std::string> m(x);
m.list();
return 0;
}
I need to write the 'set_ops' class (in testset.h). I stripped all the non-relevant parts (that otherwise work):
#pragma once
#include <iostream>
#include <set>
using namespace std;
template <class T> class set_ops
{
private:
std::set<T> elements;
public:
set_ops(std::set<T> initialSet)
{
elements = initialSet;
}
void list() const;
};
template <class T> void set_ops<T>::list() const
{
for (typename set<T>::iterator i = elements.begin(); i != elements.end(); ++i) {
cout << "\t" << *i << endl;
}
}
When I try to compile this, I get the error:
In function 'int main()':
error: no matching function for call to 'set_ops<std::__cxx11::basic_string<char> >::set_ops(std::set<std::__cxx11::basic_string<char>, string_size_less>&)'
note: candidate: set_ops<T>::set_ops(std::set<T>) [with T = std::__cxx11::basic_string<char>]|
note: no known conversion for argument 1 from 'std::set<std::__cxx11::basic_string<char>, string_size_less>' to 'std::set<std::__cxx11::basic_string<char> >'
I have tried many things and tried to find a good example, etc., but so far found none. I know for example (and tried it) that if I add another template parameter like this:
template <class T, class U = std::less<T> > class set_ops
{
private:
std::set<T, U> elements;
public:
set_ops(std::set<T, U> initialSet)
{
elements = initialSet;
}
void list() const;
};
template <class T, class U> void set_ops<T, U>::list() const
{
for (typename set<T, U>::iterator i = elements.begin(); i != elements.end(); ++i) {
cout << "\t" << *i << endl;
}
}
then if I explicitly write:
set_ops<std::string, string_size_less> m(x);
it compiles and runs without error. But, again, I MUST NOT change anything in main(), so that's not an option.
If I keep the line from the original main(), I get the same compile error even with the changes in testset.h.
I'd really like to understand the problem (and hopefully the solution) here, if anyone could help. Thanks!
This can be achieved through polymorphism and a template constructor.
#include <utility>
#include <memory>
#include <iostream>
template <typename T>
class set_ops
{
private:
// Interface type.
class set_ops_iface
{
public:
virtual ~set_ops_iface();
virtual void list() const = 0;
};
// Concrete implementation for a U.
template <typename U>
class set_ops_impl : public set_ops_iface
{
private:
U value;
public:
explicit set_ops_impl(U);
virtual void list() const override;
};
private:
// Smart pointer to interface type.
std::unique_ptr<set_ops_iface> impl;
public:
// Template constructor that can take any kind of container (not just sets)
template <typename U>
set_ops(U);
void list() const;
};
// Template constructor creates a set_ops_impl<U> owned by the interface smart pointer.
template <typename T>
template <typename U>
set_ops<T>::set_ops(U initial) :
impl{std::make_unique<set_ops_impl<U>>(std::move(initial))} { }
template <typename T>
set_ops<T>::set_ops_iface::~set_ops_iface() { }
template <typename T>
template <typename U>
set_ops<T>::set_ops_impl<U>::set_ops_impl(U initial) :
value{std::move(initial)} { }
// real list() implementation is in set_ops_impl<U>
template <typename T>
template <typename U>
void set_ops<T>::set_ops_impl<U>::list() const {
for (auto const & i : value) {
std::cout << '\t' << i << '\n';
}
}
// set_ops::list proxies to the polymorphic implementation.
template <typename T>
void set_ops<T>::list() const {
impl->list();
}
(Demo)
Note that, curiously, we don't even use the T template argument. set_ops wouldn't even need to be a template type if main() didn't require that it is.
To add another member function, you have to:
Add the implementation to the set_ops::set_ops_impl template.
Add a proxy member to set_ops.
The other answer works because std::function uses a technique called "type erasure". If you don't want to keep multiple std::functions, you can instead implement type erasure yourself.
template<typename T>
struct set_ops_erased {
virtual ~set_ops_erased() = default;
virtual void list() = 0;
};
template<typename T, typename Comp>
struct set_ops_impl : set_ops_erased<T> {
std::set<T, Comp> s;
set_ops_impl(std::set<T, Comp> s) : s(std::move(s)) { }
void list() override {
for(auto &x : s) std::cout << "\t" << x << "\n"; // endl is normally not necessary
}
};
template<typename T>
class set_ops {
std::unique_ptr<set_ops_erased<T>> ops;
public:
template<typename Comp>
set_ops(std::set<T, Comp> s)
: ops(std::make_unique<set_ops_impl<T, Comp>>(std::move(s)))
{ }
void list() { ops->list(); }
};
In this case, there's no need to worry about writing all the copy/move constructors/assignments and destructors. They just do the right thing. Because there is just one allocation of set_ops_impl, there's no need to think about using a "guarding" std::unique_ptr to clean up the container while the set_ops is being constructed.
Without changing main file, but forgetting initial order of given set (as you don't specify expected output), you might do:
template <class T> class set_ops
{
private:
std::set<T> elements;
public:
template <typename Container>
set_ops(Container& c) : elements(c.begin(), c.end()) {}
void list() const {
for (const auto& e : elements) {
std::cout << "\t" << e << std::endl;
}
}
};
I'm trying to do a templated class that runs a function only in certain cases. This is my code:
#include "stdafx.h"
#include <string>
#include <iostream>
template <class T, class U>
struct Typelist
{
typedef T Head;
typedef U Tail;
};
class NullType
{
};
typedef Typelist<int, Typelist<float, Typelist<char*, NullType> > > UsableTypes1;
typedef Typelist<short, Typelist<std::string, NullType> > UsableTypes2;
template <class T>
class MyClass
{
public:
MyClass();
private:
Typelist _types;
};
template<class T>
MyClass<T>::MyClass()
{
_types = T;
}
template<class T>
void MyClass<T>::print(T type)
{
}
MyClass<UsableTypes1> any;
I need to make the code compiler or not only if the variable I pass to the print() function is a type that is in one of the usable types. I know that probably I'll have to use std::enable_if to allow the code to compile or not if print is called with an incorrect type and std::is_same to check the types but I don't know how to combine that functions with a templated class.
This is a test class to help to explain what I want to achieve:
MyClass<UsableTypes1> one;
void TestMyClass()
{
int int_val = 0;
float flt_val = 0.1f;
const char* char_val = "Hi";
short short_val = 10;
std::string str_val = "Hello";
one.print(int_val); // OK
one.print(flt_val); // OK
one.print(char_val); // OK
// one.print( short_val); // compile error
// one.print( str_val ); // compile error
}
Specially I don't know how can I add to print() a non-T-type parameter.
I hope you can help me!
Thank you in advance.
I don't know if you have a good reason for using recursive Typelists to contain your list of types. A more straight forward way would be to make Typelist a variadic template.
Then we can add a constexpr function that returns true if a given type is part of the Typelist.
std::disjunction requires c++17, but something equivalent can be written for c++11 with a recursive template. That will however be more verbose.
#include <iostream>
#include <type_traits>
struct NullType {};
template <typename T, typename U>
struct Typelist {
using Head = T;
using Tail = U;
template <typename Type>
static constexpr bool IsUsable() {
return std::is_same<Type, T>::value;
}
};
template <typename T, typename... U>
struct Typelist<T, Typelist<U...>> {
using Head = T;
using Tail = Typelist<U...>;
template <typename Type>
static constexpr bool IsUsable() {
return std::is_same<Type, T>::value || Typelist<U...>::template IsUsable<Type>();
}
};
using UsableTypes1 = Typelist<int, Typelist<float, Typelist<const char*, NullType>>>;
template <class T>
class MyClass
{
public:
template <typename U>
void print(U u) {
static_assert(T::template IsUsable<U>(), "That is not a usable type");
std::cout << u << std::endl;
}
};
MyClass<UsableTypes1> one;
int main()
{
int int_val = 0;
float flt_val = 0.1f;
const char* char_val = "Hi";
short short_val = 10;
std::string str_val = "Hello";
one.print(int_val); // OK
one.print(flt_val); // OK
one.print(char_val); // OK
// one.print( short_val); // compile error
// one.print( str_val ); // compile error
}
My real example is quite big, so I will use a simplified one. Suppose I have a data-type for a rectangle:
struct Rectangle {
int width;
int height;
int computeArea() {
return width * height;
}
}
And another type that consumes that type, for example:
struct TwoRectangles {
Rectangle a;
Rectangle b;
int computeArea() {
// Ignore case where they overlap for the sake of argument!
return a.computeArea() + b.computeArea();
}
};
Now, I don't want to put ownership constraints on users of TwoRectangles, so I would like to make it a template:
template<typename T>
struct TwoRectangles {
T a;
T b;
int computeArea() {
// Ignore case where they overlap for the sake of argument!
return a.computeArea() + b.computeArea();
}
};
Usages:
TwoRectangles<Rectangle> x;
TwoRectangles<Rectangle*> y;
TwoRectangles<std::shared_ptr<Rectangle>> z;
// etc...
The problem is that if the caller wants to use pointers, the body of the function should be different:
template<typename T>
struct TwoRectangles {
T a;
T b;
int computeArea() {
assert(a && b);
return a->computeArea() + b->computeArea();
}
};
What is the best way of unifying my templated function so that the maxiumum amount of code is reused for pointers, values and smart pointers?
One way of doing this, encapsulating everything within TwoRectangles, would be something like:
template<typename T>
struct TwoRectangles {
T a;
T b;
int computeArea() {
return areaOf(a) + areaOf(b);
}
private:
template <class U>
auto areaOf(U& v) -> decltype(v->computeArea()) {
return v->computeArea();
}
template <class U>
auto areaOf(U& v) -> decltype(v.computeArea()) {
return v.computeArea();
}
};
It's unlikely you'll have a type for which both of those expressions are valid. But you can always add additional disambiguation with a second argument to areaOf().
Another way, would be to take advantage of the fact that there already is a way in the standard library of invoking a function on whatever: std::invoke(). You just need to know the underlying type:
template <class T, class = void>
struct element_type {
using type = T;
};
template <class T>
struct element_type<T, void_t<typename std::pointer_traits<T>::element_type>> {
using type = typename std::pointer_traits<T>::element_type;
};
template <class T>
using element_type_t = typename element_type<T>::type;
and
template<typename T>
struct TwoRectangles {
T a;
T b;
int computeArea() {
using U = element_type_t<T>;
return std::invoke(&U::computeArea, a) +
std::invoke(&U::computeArea, b);
}
};
I actually had a similar problem some time ago, eventually i opted not to do it for now (because it's a big change), but it spawned a solution that seems to be correct.
I thought about making a helper function to access underlying value if there is any indirection. In code it would look like this, also with an example similar to yours.
#include <iostream>
#include <string>
#include <memory>
namespace detail
{
//for some reason the call for int* is ambiguous in newer standard (C++14?) when the function takes no parameters. That's a dirty workaround but it works...
template <class T, class SFINAE = decltype(*std::declval<T>())>
constexpr bool is_indirection(bool)
{
return true;
}
template <class T>
constexpr bool is_indirection(...)
{
return false;
}
}
template <class T>
constexpr bool is_indirection()
{
return detail::is_indirection<T>(true);
}
template <class T, bool ind = is_indirection<T>()>
struct underlying_type
{
using type = T;
};
template <class T>
struct underlying_type<T, true>
{
using type = typename std::remove_reference<decltype(*(std::declval<T>()))>::type;
};
template <class T>
typename std::enable_if<is_indirection<T>(), typename std::add_lvalue_reference<typename underlying_type<T>::type>::type>::type underlying_value(T&& val)
{
return *std::forward<T>(val);
}
template <class T>
typename std::enable_if<!is_indirection<T>(), T&>::type underlying_value(T& val)
{
return val;
}
template <class T>
typename std::enable_if<!is_indirection<T>(), const T&>::type underlying_value(const T& val)
{
return val;
}
template <class T>
class Storage
{
public:
T val;
void print()
{
std::cout << underlying_value(val) << '\n';
}
};
template <class T>
class StringStorage
{
public:
T str;
void printSize()
{
std::cout << underlying_value(str).size() << '\n';
}
};
int main()
{
int* a = new int(213);
std::string str = "some string";
std::shared_ptr<std::string> strPtr = std::make_shared<std::string>(str);
Storage<int> sVal{ 1 };
Storage<int*> sPtr{ a };
Storage<std::string> sStrVal{ str };
Storage<std::shared_ptr<std::string>> sStrPtr{ strPtr };
StringStorage<std::string> ssStrVal{ str };
StringStorage<const std::shared_ptr<std::string>> ssStrPtr{ strPtr };
sVal.print();
sPtr.print();
sStrVal.print();
sStrPtr.print();
ssStrVal.printSize();
ssStrPtr.printSize();
std::cout << is_indirection<int*>() << '\n';
std::cout << is_indirection<int>() << '\n';
std::cout << is_indirection<std::shared_ptr<int>>() << '\n';
std::cout << is_indirection<std::string>() << '\n';
std::cout << is_indirection<std::unique_ptr<std::string>>() << '\n';
}
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;
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);
}