3

I'd like to have your opinion on how to emulate variable template as a class member. That is having a data member in your class that is dependent on a template, but not on a class template. Conceptually, something that could be written as :

class M
{
private:
    template<typename T>
    SomethingThatCouldDependOnT<T> m_dataMember;
};

My first thought was to make something like this :

#include <iostream>

class A
{
public :
    template<typename T>
    void myMethod() 
    { 
        static int a = 0;
        std::cout << "my method : " << a << " calls" << std::endl;
        a++;
    }
};


int main()
{
    A a, b;
    a.myMethod<int>();
    a.myMethod<int>();
    a.myMethod<double>();
    b.myMethod<double>();
    return 0;
}

But this doesn't work because the static member in myMethod isn't per instance but per method generated. Actually, it makes sense as myMethod can be seen as a global function taking as first parameter an object of type A (without taking account the visibility concerns). So i came up with an other idea :

#include <iostream>
#include <vector>

class A
{
public :
    A() : i(-1) {}
    template<typename T>
    void myMethod() 
    { 
        static std::vector<int> a;
        static int max = 0;
        if(i < 0)
        {
            i = max;
            a.push_back(0);
            max++;
        }
        std::cout << "my method : " << a[i] << " calls" << std::endl;
        a[i]++;
    }
private:
    int i;
};


int main()
{
    A a, b;
    a.myMethod<int>();
    a.myMethod<int>();
    a.myMethod<double>();
    b.myMethod<double>();
    return 0;
}

But I don't really like it, even if it could be improved by reusing integers unused or using a more appropriate container.

I want to think that it might have a better solution for my problem here, that's why i'm asking.

Nemikolh
  • 700
  • 4
  • 19
  • Wait for C++14, which will add variable templates to the language. Only one more year, how long could it possibly take? :-) – Kerrek SB May 18 '13 at 15:28
  • 1
    Your question is still unclear as to what you wish to achieve? – Ahmed Masud May 18 '13 at 15:31
  • 1
    This isn't possible in the general case, as the compiler couldn't know the size of your class. Perhaps there's an alternative solution to the original problem you're solving, which does not need a template data member? – Angew is no longer proud of SO May 18 '13 at 15:39
  • @ Angew : I don't understand why the compiler couldn't know the size of my class. Template are resolved during compilation-time, and do not include any RTTI cost, so having template data member doesn't seems unachievable to me. :) @ KerrekSB : I'm glad to know they'll add such a feature to the language ! :-) @ AhmedMasud : The question came up to me when trying to do a similar EventBus than the one made in GWT. – Nemikolh May 18 '13 at 17:20
  • If it was a _class template_, with dependent data members, then the compiler would know its size. If you want data members to be spawned in a non-template class when the compiler sees a template member function instantiation, then the compiler might not know the size of the class, because you might pass an instance of the class by reference to another compilation unit where the template instantiation is not visible. – Oktalist May 18 '13 at 20:44

2 Answers2

4

You can do this by creating a map from a type_info pointer to your type-specific data.

Here's an example:

#include <iostream>
#include <map>
#include <typeinfo>

// Custom comparison operator that uses the std::type_info::before member
// function.  Comparing the pointers doesn't work since there is no
// guarantee that the typeid operator always gives you the same object
// for the same type.
struct BeforeType {
  bool operator()(const std::type_info *a,const std::type_info *b) const
  {
    return a->before(*b);
  }
};

struct A {
  template <typename T>
  int &member()
  {
    return member_map[&typeid(T)];
  }

  std::map<const std::type_info *,int,BeforeType> member_map;
};

int main(int,char**)
{
  A a1, a2;
  ++a1.member<int>();
  ++a1.member<int>();
  ++a1.member<double>();
  ++a2.member<int>();
  std::cout << a1.member<int>() << "\n";
  std::cout << a1.member<double>() << "\n";
  std::cout << a1.member<float>() << "\n";
  std::cout << a2.member<int>() << "\n";
  return 0;
}

The output is:

2
1
0
1

If you are interested in a container of values of different types, you could use something like this:

#include <iostream>
#include <map>
#include <typeinfo>


struct BeforeType {
  bool operator()(const std::type_info *a,const std::type_info *b) const
  {
    return a->before(*b);
  }
};

struct Value {
  virtual ~Value() { }
  virtual Value *clone() = 0;
};

template <typename T>
struct BasicValue : Value {
  T value;
  BasicValue() : value() { }
  BasicValue(const T &value) : value(value) { }
  virtual Value *clone() { return new BasicValue(value); }
};

struct TypeMap {
  TypeMap() { }

  TypeMap(const TypeMap &that)
  {
    add(that.value_map);
  }

  template <typename T>
  T &value()
  {
    ValueMap::iterator iter = value_map.find(&typeid(T));
    if (iter==value_map.end()) {
      BasicValue<T> *member_ptr = new BasicValue<T>;
      value_map.insert(ValueMap::value_type(&typeid(T),member_ptr));
      return member_ptr->value;
    }
    return static_cast<BasicValue<T> *>(iter->second)->value;
  }

  TypeMap &operator=(const TypeMap &that)
  {
    clear();
    add(that.value_map);
    return *this;
  }

  void clear()
  {
    while (!value_map.empty()) {
      Value *member_ptr = value_map.begin()->second;
      value_map.erase(value_map.begin());
      delete member_ptr;
    }
  }

  ~TypeMap()
  {
    clear();
  }

  private:    
    typedef std::map<const std::type_info *,Value *,BeforeType> ValueMap;
    ValueMap value_map;

    void add(const ValueMap &value_map)
    {
      ValueMap::const_iterator iter = value_map.begin(), end = value_map.end();
      for (;iter!=end;++iter) {
        this->value_map[iter->first] = iter->second->clone();
      }
    }
};

int main(int,char**)
{
  TypeMap type_map;
  type_map.value<int>() = 5;
  type_map.value<float>() = 2.5;
  type_map.value<std::string>() = "hi";
  std::cout << type_map.value<int>() << "\n";
  std::cout << type_map.value<float>() << "\n";
  std::cout << type_map.value<std::string>() << "\n";
  return 0;
}

The output is:

5                                                                                      
2.5                                                                                    
hi  

However, if you are using boost, this can be simplified significantly:

struct TypeMap {
  template <typename T>
  T &value()
  {
    boost::any &any_value = value_map[&typeid(T)];
    if (any_value.empty()) {
      any_value = T();
    }
    return *boost::any_cast<T>(&any_value);
  }

  private:
    std::map<const std::type_info *,boost::any,BeforeType> value_map;
};

With C++11, you can also get rid of the custom comparison by using std::type_index:

struct TypeMap {
  template <typename T>
  T &value()
  {
    boost::any &any_value = value_map[std::type_index(typeid(T))];
    if (any_value.empty()) {
      any_value = T();
    }
    return *boost::any_cast<T>(&any_value);
  }

  private:
    std::map<const std::type_index,boost::any> value_map;
};
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • Your answer is quite interesting, but works only if, like in the example I gave, the member doesn't depend on the template. Actually my example wasn't quite relevant for the general case, which is moer "how to have a data member that is generated by a template and may depend on it". – Nemikolh May 18 '13 at 17:43
  • @Nemikolh What do you mean, "the member doesn't depend on the template"? – Oktalist May 18 '13 at 19:06
  • @Nemikolh: It's entirely possible to have the type be mapped to a pointer to a data value of that type. – Vaughn Cato May 19 '13 at 03:45
  • @Nemikolh: or more precisely, be mapped to a void pointer, which is pointing to an object of the appropriate type, which you can static_cast to. – Vaughn Cato May 19 '13 at 04:21
  • @VaughnCato Thanks for your reply, the void pointer was the missing element to have a complete answer to my question. :-) So to recap, the map should be more like : std::map But now, how should i delete my void pointer ? I can't use a smart pointer here, right ? – Nemikolh May 19 '13 at 19:09
  • After reading [this](http://stackoverflow.com/questions/941832/is-it-safe-to-delete-a-void-pointer), I finally doesn't like so much the idea of the void pointer. But if there is no other solution than waiting for C++14, then let's wait. – Nemikolh May 19 '13 at 19:19
  • @Nemikolh: The proposal for variable templates in C++14 only applies to constants or static members, not regular members of a class. – Vaughn Cato May 20 '13 at 05:40
  • @Nemikolh: Instead of a void pointer, you'll probably want to have a base class with a virtual destructor so the memory can be cleaned up properly. I've added an example to my answer. – Vaughn Cato May 20 '13 at 06:10
  • @VaughnCato I like your final solution ! True [variables](http://isocpp.org/files/papers/N3651.pdf) templates would not have bring a cleaner answer. I'll mark your answer as the accepted answer. – Nemikolh May 20 '13 at 20:46
  • The implementation is rather poor. It may leak memory, prone to double deletion of values. Scratch that and use `boost::variant<>` or something similar instead. – Maxim Egorushkin May 21 '13 at 08:32
  • @MaximYegorushkin: `boost::variant<>` is for a single value of any type, but the OP wants a value for each type. Are you suggesting a map from `std::type_info *` to a `boost::variant<>`? – Vaughn Cato May 21 '13 at 13:34
  • @VaughnCato I meant that `Value` can be replaced with `boost::variant<>` or `boost::any`. – Maxim Egorushkin May 21 '13 at 13:40
  • @MaximYegorushkin: That is a lot better. I've added an example to my answer. – Vaughn Cato May 21 '13 at 14:17
  • Better to use `std::type_index`. The `type_info` objects aren't guaranteed to be unique. – Potatoswatter May 21 '13 at 14:19
  • @Potatoswatter: I had a custom comparison to handle that, but I agree `std::type_index` is nicer. I've added that to my answer. – Vaughn Cato May 21 '13 at 14:25
  • @VaughnCato With `boost::any` it is much better. Note, that `value()` may end up dereferencing a null pointer, since it employs `T* any_cast(any*)` which returns a null pointer if `T` is wrong. – Maxim Egorushkin May 21 '13 at 14:29
  • @MaximYegorushkin In which case T might be "wrong" ? Because it seems that if T can't be constructed you'll get a compile-time error, or maybe this is not what you meant. @ VaughnCato Thanks for updating your answer, I really like your C++11 answer. :-) – Nemikolh May 21 '13 at 20:21
  • @Nemikolh My bad, `T` can't be wrong there since `T` is used to look up that `any` value. – Maxim Egorushkin May 22 '13 at 08:18
1

All you need is an extra template with a static member:

#include <iostream>

template<class T>
struct StaticForMyMethod
{
    static int value;
};

template<class T>
int StaticForMyMethod<T>::value;

template<typename T>
void myMethod()
{
    int& a = StaticForMyMethod<T>::value;
    std::cout << "my method : " << a << " calls" << std::endl;
    a++;
}

int main() {
    myMethod<int>();
    myMethod<int>();
    myMethod<long>();
    myMethod<long>();
}

Outputs:

my method : 0 calls
my method : 1 calls
my method : 0 calls
my method : 1 calls
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271