I have the following code where I try to detect what concept(member functions in this case) alternative in variant holds.
It is verbose and relatively ugly.
Is there a nicer way to do this?
Note that I do not want to use inheritance, and I do not want to use static polymorphism(let's assume what engine is used is not known at compile time).
In simple terms I am looking for runtime mix of if constexpr(that only checks concepts at compile time) and std::holds_alternative (that can only check for if specific type is in variant, not if any of the types satisfying the concept is in variant).
#include <iostream>
#include <variant>
struct simple_engine1{
};
struct simple_engine2{
};
struct complex_engine1{
void reduce_thrust(int perc){
std::cout<<"reducing thrust " << perc << "% " << std::endl;
}
};
struct complex_engine2{
void reduce_thrust(int perc){
std::cout<<"reducing thrust " << perc << "% " << std::endl;
}
};
template< class, class = std::void_t<> >
struct has_reduce_thrust : std::false_type { };
template< class T >
struct has_reduce_thrust<T,
std::void_t<decltype( std::declval<T>().reduce_thrust(42) )>
> : std::true_type { };
static_assert(!has_reduce_thrust<simple_engine1>::value);
static_assert(!has_reduce_thrust<simple_engine1>::value);
static_assert(has_reduce_thrust<complex_engine1>::value);
struct visitor{
template<typename T>
void operator()(T& t){
dispatch(t, has_reduce_thrust<T>{});
}
template<typename T>
void dispatch(T& t, std::true_type /*has_reduce_thrust*/){
t.reduce_thrust(perc);
reduced_thrust=true;
}
template<typename T>
void dispatch(T& , std::false_type){
reduced_thrust=false;
}
int perc = 0;
bool reduced_thrust = false;
};
// tries to reduce speed by reducing thrust if engine supports it, if not
// it fires reverse engines(more expensive wrt fuel usage)
void reduce_speed(std::variant<simple_engine1, simple_engine2, complex_engine1, complex_engine2>* var_engine){
visitor v;
v.perc = 47;
std::visit(v, *var_engine);
if (v.reduced_thrust) {
std::cout << "reduced thrust\n";
} else {
std::cout << "activating reverse engines\n";
}
}
int main() {
std::variant<simple_engine1, simple_engine2, complex_engine1, complex_engine2> var_engine{simple_engine1{}};
reduce_speed(&var_engine);
var_engine = complex_engine2{};
reduce_speed(&var_engine);
var_engine = simple_engine2{};
reduce_speed(&var_engine);
var_engine = complex_engine2{};
reduce_speed(&var_engine);
}
You can simplify the visitor a lot by using if constexpr:
struct visitor{
template<typename T>
void operator()(T& t) {
if constexpr (has_reduce_thrust<T>::value) {
t.reduce_thrust(perc);
reduced_thrust = true;
}
else {
reduced_thrust = false;
}
}
int perc = 0;
bool reduced_thrust = false;
};
You could then abstract further by accepting any predicate and two functions for either branch of the if constexpr:
template <template <class, class... /*SFINAE friendly*/> class TypePred,
class MatchedFunc, class UnmatchedFunc>
class predicated_visitor {
public:
predicated_visitor(MatchedFunc matchedFunc, UnmatchedFunc unmatchedFunc)
: _matchedFunc(matchedFunc), _unmatchedFunc(unmatchedFunc) {}
template <typename T>
void operator()(T& t) {
if constexpr (TypePred<T>::value)
_matchedFunc(t);
else
_unmatchedFunc(t);
}
private:
MatchedFunc _matchedFunc;
UnmatchedFunc _unmatchedFunc;
};
template <template <class, class... /*SFINAE friendly*/> class TypePred,
class F1, class F2>
auto makePredicatedVisitor(F1 f1, F2 f2) {
return predicated_visitor<TypePred, F1, F2>(f1, f2);
}
The resulting code is quite nice I feel:
void reduce_speed(std::variant<simple_engine1, simple_engine2, complex_engine1,
complex_engine2>* var_engine) {
int perc = 47;
bool reducedThrust = false;
auto reduceableThrustAction = [perc, &reducedThrust](auto& t) {
t.reduce_thrust(perc);
reducedThrust = true;
};
auto alternativeAction = [](auto& t) {
}; // Could explicitly set reduceThrust to false for clarity.
auto thrust_visitor = makePredicatedVisitor<has_reduce_thrust>(
reduceableThrustAction, alternativeAction);
std::visit(thrust_visitor, *var_engine);
if (reducedThrust) {
std::cout << "reduced thrust\n";
} else {
std::cout << "activating reverse engines\n";
}
}
Demo
This example compiles to essentially the same assembly code as yours but can be reused in any way you like.
I apologize for the inconsistent capitalization...
Related
I'm trying to use template specialization to return different types based on the value of the template variable.
I've moved from trying to branch at runtime rather than compile time using typeof(), unspecialized templates and using std::enable_if_t<>. I think this may stem from a lack of understanding about how the template functions are resolving.
class Test
{
public:
template <typename T>
T request()
{
T ret = getVal<T>();
return ret;
}
private:
float foo = 2.f;
int bar = 1;
template <typename T>
typename std::enable_if<std::is_same<T, float>::value, bool>::type
getVal() { return foo; }
template <typename T>
typename std::enable_if<std::is_same<T, int>::value, bool>::type
getVal() { return bar; }
template<typename T>
T getVal()
{
std::cout << "T is type " << typeid(T).name() << std::endl;
throw std::bad_typeid();
}
};
int main()
{
Test t;
int i;
float f;
i = t.template request<int>();
f = t.template request<float>();
}
I'm expecting this to resolve to three different functions, but I'm not sure if it is:
T Test::getVal()
int Test::getVal()
float Test::getVal()
Any help would be greatly appreciated.
You can do this pretty easily by specializing getVal(), though you may have oversimplified the question
class Test {
public:
template <typename T>
T request() {
T ret = getVal<T>();
return ret;
}
private:
float foo = 2.f;
int bar = 1;
template<typename T>
T getVal() {
std::cout << "T is type " << typeid(T).name() << std::endl;
throw std::bad_typeid();
}
};
// add specializations
template<>
float Test::getVal<float>() { return foo; }
template <>
int Test::getVal<int>() { return bar; }
int main() {
Test t;
int i = t.request<int>(); // don't need template keyword here
float f = t.request<float>();
}
If you're able to use c++17, it is even simpler with if constexpr and a single getVal
template<typename T>
auto getVal() {
if constexpr (std::is_same_v<int, T>) {
return foo;
} else if constexpr (std::is_same_v<float, T>) {
return bar;
} else {
std::cout << "T is type " << typeid(T).name() << std::endl;
throw std::bad_typeid();
}
}
Your problem is that template<typename T> T getVal() make call ambiguous when the SFINAEd one succeed.
One solution is to restrict that one with complement condition...
But tag dispatching is an easy alternative way to fix your issue:
template <typename> struct Tag{};
class Test
{
public:
template <typename T>
T request() const
{
return getVal(Tag<T>{});
}
private:
float foo = 2.f;
int bar = 1;
float getVal(Tag<float>) const { return foo; }
int getVal(Tag<int>) const { return bar; }
template<typename T> void getVal(Tag<T>) = delete;
};
I want to store an arbitrary number of different classes (all shared-pointered) in some manager-class. The different classes have to be derived from the same CRTP-interface-class. Finally, I want to be able to iterate over all stored classes and calling some functions of the interface. I do not want to create a common base class and I want only use compile-timed stuff.
So I read a few articles in the internet and stole some concepts together. Now I have a working solution (I hope, I am new to templates in C++!), but I think, that is by far too much overkill for such a "simple" requirement.
Can you plz help me to optimize (simplify/shrink/modify) the following minimal example or provide some smarter solution? (no boost and only C++11/14/17)
#include <iostream>
#include <tuple>
#include <memory>
template <class T_IMPLEMENTATION>
struct ISystem {
bool run(int i) { return static_cast<T_IMPLEMENTATION*>(this)->runImpl(i); }
};
struct SystemA : public ISystem<SystemA> {
friend ISystem<SystemA>;
private:
bool runImpl(int i) { std::cout << this << " A:" << i << std::endl; return i; };
};
struct SystemB : public ISystem<SystemB> {
friend ISystem<SystemB>;
private:
bool runImpl(int i) { std::cout << this << " B:" << i << std::endl; return i; };
};
template<typename... ARGS>
struct SystemManager {
template <int index, typename... Ts>
struct runSystem {
void operator()(std::tuple<Ts...>& t, int i) {
std::get<TUPLE_SIZE - index - 1>(t)->run(i++);
runSystem<index - 1, Ts...>{}(t, i);
}
};
template <typename... Ts>
struct runSystem<0, Ts...> {
void operator()(std::tuple<Ts...>& t, int i) {
std::get<TUPLE_SIZE - 1>(t)->run(i);
}
};
template <typename...SPTR_ARGS>
void addSystems(SPTR_ARGS...args) {
m_tupleSystems = std::make_tuple(args...);
}
void run() {
m_value = 0;
runSystem<TUPLE_SIZE - 1, std::shared_ptr<ISystem<ARGS>>...>{}(m_tupleSystems, m_value);
}
private:
using TUPLE_TYPE = std::tuple<std::shared_ptr<ISystem<ARGS>>...>;
static constexpr auto TUPLE_SIZE = std::tuple_size<TUPLE_TYPE>::value;
TUPLE_TYPE m_tupleSystems;
int m_value;
};
int main() {
auto sptrSystemA = std::make_shared<SystemA>();
auto sptrSystemB = std::make_shared<SystemB>();
SystemManager<SystemA, SystemB> oSystemManager;
oSystemManager.addSystems(sptrSystemA, sptrSystemB);
oSystemManager.run();
return 0;
}
How to enclose std::is_base_of in a std::function practically?
I don't think it is possible, because the type is erased.
How to workaround? I want to cache std::function f=check whether X derived from B.
It will be called later.
#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
class B{ };
class C: B{};
int main(){
std::cout<<std::is_base_of<B,C>()<<std::endl; //print 1
std::function<bool()> f=[](X)->bool{ //<--- syntax error (X is class name)
return std::is_base_of<B,X>();
};
std::cout<<f(C)<<std::endl; //<--- possible? (should print 1)
return 0;
}
In real case, f is called in a far-away location of code, and I don't want to instantiate B or C.
(Thus, I can't use dynamic_cast to check.)
(Edit) Underlying Problem :-
I am trying to get a 2D-bool table (result #) for some sophisticated types manipulation.
class B{};
class C : B{};
class D{};
int main(){
for(auto cachefunction : cacheFunctions){
cachefunction();
//^ "cacheFunctions" is {register<B>(), register<C>(), register<D>()}
}
//in real case, cacheFunctions.size() ~ 200+
auto result=.... //#
}
I can edit code inside register<T>() to whatever I want, but I can't request user to call register<T1,T2> for every possible tuple.
Roughly speaking, the result # is an array of bool flag whether T2 derived from T1.
B C D (parent)
------------
(derived)
B x 0 0
C 1 x 0
D 0 0 x (x=don't care, 0 or 1 are OK)
int[]/std::vector<int> result = {x,0,0 , 1,x,0 , 0,0,x}.
My ultimate goal is to get the table at the line //#.
(Edit) Underlying Problem 2nd attempt :-
I have a user code that calls my library like this.
These lines are scattering around many user's .cpp :-
requestSystem<S1>()->someBFunction();
requestSystem<S2>()->someCFunction();
....
Si are sub-systems in my library (Here, I use composition rather that inheritance.).
So far, I have successfully used some trick (array of std::function) to instantiate those sub-system (new Si()), before those functions are actually called at run-time. Thus, it runs fine.
As my program grow, more Si are born.
I notice that there are appropriate cases where some Sy are best to inherit from a certain Sx.
In such cases, I find that if I instantiate both new Sx() and new Sy(), my program will act strangely, because there are two instance of base class Sx (it should be singleton by design).
I think it would be nice if I can detect such cases automatically by embedding some additional code inside requestSystem<T>() to instantiate only new S_Y(), and let requestSystem<S_X>() return the same pointer to S_Y.
I can't use the same trick (array of std::function) for std::is_base_of to check inheritance because the type is erased. Moreover, I intend to not call new S_X(), so I can't cache the type by its instance and use dynamic_cast later.
Here is MCVE (ideone).
The first part is Manager's definition:-
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
class Manager{
public: Manager(){std::cout<<"Manager"<<std::endl; }
};
class EntityManager : public Manager{
public: EntityManager(){std::cout<<"EntityManager"<<std::endl;}
};
class AdvanceEntityManager : public EntityManager{
public: int testField=5; //just for testing
public: AdvanceEntityManager(){
std::cout<<"AdvanceEntityManager"<<std::endl;
}
};
Here is the type-manipulator :-
template<class T> class DummyHook{ public: static int dummyInt; };
template<class T> class IndexCache{ public: static int index; };
//^ index = index of T* inside "globalManagerList"
std::vector<Manager*> globalManagerList;
std::vector<std::function<Manager*(int)>>* getFunctionPointers(){
static std::vector<std::function<Manager*(int)>> cacher;
return &cacher;
}
/** it is called indirectly by "requestSystem" */
template<class T> int indirectCall(){
std::function<Manager*(int)> func=[](int assignIndex){
IndexCache<T>::index = assignIndex;
auto re= new T();
globalManagerList.push_back(re);
return re;
};
getFunctionPointers()->push_back(func);
int dummy=42;return dummy;
}
template<class T> T* requestSystem(){
int k=DummyHook<T>::dummyInt;
//^ optimized out, but force calling "indirectCall()" before main() at #
return static_cast<T*>(globalManagerList[IndexCache<T>::index]);
}
template<class T> int DummyHook<T>::dummyInt = indirectCall<T>(); //#
template<class T> int IndexCache<T>::index = -42;
Here is the main function :-
int main() {
auto fs=getFunctionPointers();
int n=0;
for(auto ele: *fs){
ele(n); ++n;
//^ call every function in
// static std::vector<std::function<Manager*(int)>> cacher
}
std::cout<<"All initialized, ready!"<<std::endl;
auto entityManagerPtr=requestSystem<EntityManager>();
auto advanceManagerPtr=requestSystem<AdvanceEntityManager>();
//^ In this program, they are different instance, but I want it to be the same instance.
std::cout<<"Should be 5 : "<<advanceManagerPtr->testField<<std::endl;
return 0;
}
This is the output (both managers are instantiated :():-
Manager
EntityManager
Manager
EntityManager
AdvanceEntityManager
All initialized, ready!
Should be 5 : 5
The list of bases of a type is not avaliable at runtime, which is what your code in effect requires.
We can however write our own typesystem. In this case, we mark up each type with a list of each parent, and enable compile time reflection over that list.
Then when we set up the factories, we also replace the factories for the parent types, and ensure that only the child type is used.
Here is some random tools to help:
template<class...Ts> struct types_t {};
template<class T> struct tag_t { constexpr tag_t() {} using type=T; };
template<class T> constexpr tag_t<T> tag{};
template<class Tag> using type_t=typename Tag::type;
template<std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f)->decltype(auto) {
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
return index_over( std::make_index_sequence<N>{} );
}
template<class F>
auto invoke_foreach( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args){
using discard=int[];
(void)discard{0,(void(
f(decltype(args)(args))
),0)...};
};
}
template<class F>
auto invoke_on_tuple( F&& f ) {
return [f=std::forward<F>(f)](auto&& tuple)->decltype(auto) {
using Tuple = decltype(tuple);
using dTuple = std::decay_t<Tuple>;
using Size = std::tuple_size<dTuple>;
return index_upto<Size{}>()( [&](auto&&...args)->decltype(auto){
return f( std::get<decltype(args){}>( decltype(tuple)(tuple) )... );
});
};
}
template<class...Ts>
constexpr std::tuple< tag_t<Ts>... > as_tuple_tags( types_t<Ts...> ) { return std::make_tuple(tag<Ts>...); }
template<class F>
struct y_combinator_t {
F f;
template<class...Args>
decltype(auto) operator()( Args&&... args ) {
return f(*this, std::forward<Args>(args)...);
}
};
template<class F>
y_combinator_t<std::decay_t<F>> y_combinate( F&& f ) { return {std::forward<F>(f)}; }
Now, we mark up types with their parents:
class Manager{
public: Manager(){std::cout<<"Manager"<<std::endl; }
};
class EntityManager : public Manager{
public:
int base_value = 3;
EntityManager(){std::cout<<"EntityManager"<<std::endl;}
using parents = types_t<Manager>;
};
class AdvanceEntityManager : public EntityManager{
public: int testField=5; //just for testing
public:
AdvanceEntityManager(){
std::cout<<"AdvanceEntityManager"<<std::endl;
base_value = 1;
}
using parents = types_t<EntityManager>;
};
and use the starting code to let us easily work with parents:
template<class T, class Parents = typename T::parents>
auto foreach_parent( tag_t<T> ) {
constexpr auto parent_tuple = as_tuple_tags( Parents() );
return [](auto&& f) {
return invoke_on_tuple(invoke_foreach(f))(decltype(parent_tuple){});
};
}
template<class T, class...Ts>
auto foreach_parent( tag_t<T>, Ts&&... ) {
return [](auto&& f) {};
}
we set up a two-entry cache, a singleton factory, and use smart pointers:
template<class T> class IndexCache{
public:
static int index;
static int factory;
};
template<class T> class Dummy{ public: static int index; };
using pManager = std::shared_ptr<Manager>;
using ManagerFactory = std::function<pManager()>;
using Task = std::function<void()>;
std::vector<pManager> globalManagerList;
std::vector<Task>& getManagerFactories(){
static std::vector<Task> cacher{};
return cacher;
}
template<class T>
ManagerFactory singleton_factory() {
return []{
static auto single = (void(std::cout << "making " << typeid(T).name() << " singlton" << std::endl), std::make_shared<T>());
return single;
};
}
and modify indirect call to replace the parent factory tasks:
template<class T>
void indirectCall(){
std::cout << "Setting up " << typeid(T).name() << std::endl;
auto func=[](auto tag){
return [tag](){
IndexCache<type_t<decltype(tag)>>::index = globalManagerList.size();
globalManagerList.push_back(singleton_factory<T>()());
};
};
//std::cout << "Adding " << typeid(T).name() << " factory " << std::endl;
IndexCache<T>::factory = getManagerFactories().size();
getManagerFactories().push_back(func(tag<T>));
auto replace_parents = y_combinate(
[&](auto& replace_parents, auto child_tag) {
foreach_parent(child_tag)([&](auto parent_tag){
using Parent = type_t<decltype(parent_tag)>;
std::cout << "Replacing " << typeid(Parent).name() << " factory with " << typeid(T).name() << " factory" << std::endl;
getManagerFactories()[IndexCache<Parent>::factory] = func(tag<Parent>);
replace_parents( parent_tag );
});
}
);
replace_parents(tag<T>);
std::cout << "Added " << typeid(T).name() << " factory " << std::endl;
}
In requestSystem, we ensure that all of the setup has been done:
void setup_code() {
for (auto&& factory:getManagerFactories())
factory();
}
void setup() {
static int unused = (setup_code(),7);
(void)unused;
}
template<class T>
T* requestSystem()
{
int dummy = Dummy<T>::index;
(void)dummy;
std::cout << "Requesting " << typeid(T).name() << std::endl;
setup();
return static_cast<T*>(globalManagerList[IndexCache<T>::index].get());
}
template<class T> int IndexCache<T>::index = -1;
template<class T> int IndexCache<T>::factory = -1;
template<class T> int Dummy<T>::index = (indirectCall<T>(), 7);
and then we test it:
int main() {
std::cout<<"All initialized, ready!"<<std::endl;
auto entityManagerPtr=requestSystem<EntityManager>();
//std::cout<<"Phase 1"<<std::endl;
(void)entityManagerPtr;
auto advanceManagerPtr=requestSystem<AdvanceEntityManager>();
//std::cout<<"Phase 2"<<std::endl;
//^ In this program, they are different instance, but I want it to be the same instance.
std::cout<<"Should be 5 : "<<advanceManagerPtr->testField<<std::endl;
std::cout<<"Should be 1 : "<<entityManagerPtr->base_value<<std::endl;
return 0;
}
here we ask for the entityManagerPtr, and we get the advanceManagerPtr. We can see from the logs that only one object was created, and its entityManagerPtr->base_value was 1.
Live example.
I want to specialise a single template method in a non-template class to use an std::vector however only the return type of the method uses the template.
#include <iostream>
#include <string>
#include <vector>
class Foo
{
public:
template<typename T>
T Get()
{
std::cout << "generic" << std::endl;
return T();
}
};
template<>
int Foo::Get()
{
std::cout << "int" << std::endl;
return 12;
}
template<typename T>
std::vector<T> Foo::Get()
{
std::cout << "vector" << std::endl;
return std::vector<T>();
}
int main()
{
Foo foo;
auto s = foo.Get<std::string>();
auto i = foo.Get<int>();
}
This compiles with an error indicating that the std::vector attempted specialisation does not match any prototype of Foo, which is completely understandable.
In case it matters, use of C++14 is fine and dandy.
You can only partially specialize classes (structs) (cppreference) - so the way to overcome your problems is to add helper struct to allow this partial specialization of std::vector<T> - e.g. this way:
class Foo
{
private: // might be also protected or public, depending on your design
template<typename T>
struct GetImpl
{
T operator()()
{
std::cout << "generic" << std::endl;
return T();
}
};
public:
template<typename T>
auto Get()
{
return GetImpl<T>{}();
}
};
For int - you can fully specialize this function:
template<>
int Foo::GetImpl<int>::operator()()
{
std::cout << "int" << std::endl;
return 12;
}
For std::vector<T> you have to specialize entire struct:
template<typename T>
struct Foo::GetImpl<std::vector<T>>
{
std::vector<T> operator()()
{
std::cout << "vector" << std::endl;
return std::vector<T>();
}
};
Partial specialisation of template functions (including member functions) is not allowed. One option is to overload instead using SFINAE. For example,
/// auxiliary for is_std_vetor<> below
struct convertible_from_std::vector
{
template<typename T>
convertible_from_std::vector(std::vector<T> const&);
};
template<typename V>
using is_std_vector
= std::is_convertible<V,convertible_from_std_vector>;
class Foo
{
public:
template<typename T, std::enable_if_t< is_std::vector<T>::value,T>
Get()
{
std::cout << "vector" << std::endl;
return T();
}
template<typename T, std::enable_if_t<!is_std::vector<T>::value,T>
Get()
{
std::cout << "generic" << std::endl;
return T();
}
};
Note that the helper class is_std_vector may be useful in other contexts as well, so it worth having somewhere. Note further that you can make this helper class more versatile by asking for any std::vector or specific std::vector<specific_type, specific_allocator>. For example,
namespace traits {
struct Anytype {};
namespace details {
/// a class that is convertible form C<T,T>
/// if either T==AnyType, any type is possible
template<template<typename,typename> C, typename T1=Anytype,
typename T2=Anytype>
struct convCtTT
{
convCtTT(C<T1,T2> const&);
};
template<template<typename,typename> C, typename T1=Anytype>
struct convCtTT<C,T1,AnyType>
{
template<typename T2>
convCtTT(C<T1,T2> const&);
};
template<template<typename,typename> C, typename T2=Anytype>
struct convCtTT<C,AnyType,T2>
{
template<typename T1>
convCtTT(C<T1,T2> const&);
};
template<template<typename,typename> C>
struct convCtTT<C,AnyType,AnyType>
{
template<typename T1, typename T2>
convCtTT(C<T1,T2> const&);
};
}
template<typename Vector, typename ValueType=AnyType,
typename Allocator=AnyType>
using is_std_vector
= std::is_convertible<Vector,details::convCtTT<std::vector,ValueType,
Allocator>;
}
You can't partially specialze template in c++. You need to overload your function and pass the type in parameters.
#include <iostream>
#include <string>
#include <vector>
class Foo
{
public:
template<typename T>
T Get()
{
return this->getTemplate(static_cast<T*>(0)); //
}
private:
template<class T> T getTemplate(T* t)
{
std::cout << "generic" << std::endl;
return T();
}
template<class T> std::vector<T> getTemplate(std::vector<T>* t)
{
std::cout << "vector" << std::endl;
return std::vector<T>();
}
};
template <> int Foo::getTemplate(int* t)
{
std::cout << "int" << std::endl;
return 12;
}
int main()
{
Foo foo;
auto s = foo.Get<std::string>();
auto i = foo.Get<int>();
auto v = foo.Get<std::vector<int>>();
}
Edit : fixed a typo in the code
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';
}