[C++] Template argument restrictions

Started by
12 comments, last by King Mir 16 years, 3 months ago
I am trying to make a class that registers up to one instance of any single object. More concretely, I want my WidgetRegister class to implement an api like this:

struct WidgetRegister
{
  template<class T>
  void registerWidget(T *widget);

  template<class T>
  T *getWidget();

  template<class T>
  void unregisterWidget();
}
Now, I know that I can't convert T to a string through simple language support, so I plan to make a base Widget subclass like this:

struct Widget
{
  static std::string getName() { return "baseWidget"; }
}
There are a few problems I'm facing though: 1) What is the best way to ensure that T can only be a Widget or subclass of WidgetRegister? I am finding now that if I pass an int or some other object, calling T::getName() will fail as long as it doesn't implement getName(), but will have an arguably non-intuitive error. But even worse, if it does have getName(), the template function will move ahead with whatever getName() returned even though T doesn't inherit from Widget. I guess I could mangle getName() to something longer and more specific to prevent accidents but this doesn't feel "right." I think the best solution would be some construct in the declaration like template<class T : public Widget> ... Is there a language construct like this that I can use? 2) Another nasty: while getWidget() and unregisterWidget() can't deduce T and require <MyWidget> explicitly stated, registerWidget *can* deduce T. If I do:

  WidgetExtended *widgetExtended = new WidgetExtended();
  Widget *base = widgetExtended;
  addGameComponent(base);
registerWidget(T *) will use Widget::getName() instead of WidgetExtended::getName() and thus be WRONG! Can I enforce that the user call the function with <>s? I can completely sidestep this issue by using virtual functions but now I have to have a T* instantiated to get the name. This will work fine for the register() but not at all for the get and remove. 3) If I continue using the static T::getName() function, is there a way I can enforce subclasses to implement a static T::getName() function also similar in effect to pure virtual functions? i.e.

class MyWidget : Widget
{
  //COMPILE ERROR: because getName() not defined
}
Here the code would not compile if MyWidget was registered with the WidgetRegister but would give a non-intuitive error. Worse, if WidgetRegister and MyWidget never interact, the code WOULD compile. So, I'm not sure if I should even go this route. I NEED to dynamically instantiate as many widgets as I want, and I NEED to be able to refer to them by string name. I do NOT want typecasting anywhere (I'm going for a completely type safe solution if possible). I think this almost works, but it seems like too much trouble for what overall is kind of a hack. While the outside interface is fairly simple, elegant, and very type safe, the internals are more complicated than I would like. Even if all the problems above were solved, the code would still be non-trivial to maintain. Am I trying too hard? Is there a better way?
Advertisement
You could look at boost::enable_if or similar SFINAE.
For 1 and 2, the easiest way to make certain that the T * is a widget is to assign it to a Widget pointer, inside the function. Ex:
  template<class T>  void registerWidget(T *widget) {    Widget * make_sure_im_a_widget = widget;    // stuff  }

However, you can instead use:
  template<class T>  void registerWidget(Widget * widget) {    // stuff  }

Now, it can no longer deduce the template from the arguments, so the user is forced to use a explicit instantiation and the widget needs to be derived from Widget.

However, what you seem to be doing is implementing your own RTTI facilities. This is pretty silly since C++ contains perfectly workable RTTI. An example of what you seem to be trying to do, using RTTI instead:
#include <typeinfo>#include <map>#include <iostream>class TypeInfoProxy {  public:    TypeInfoProxy(const std::type_info * t) : ti(t) {}    const std::type_info * ti;};inline bool operator<(const TypeInfoProxy & lhs, const TypeInfoProxy & rhs) {  return lhs.ti->before(*rhs.ti) != 0;}inline bool operator>(const TypeInfoProxy & lhs, const TypeInfoProxy & rhs) {  return rhs.ti->before(*lhs.ti) != 0;}inline bool operator<=(const TypeInfoProxy & lhs, const TypeInfoProxy & rhs) {  return !(rhs > lhs);}inline bool operator>=(const TypeInfoProxy & lhs, const TypeInfoProxy & rhs) {  return !(rhs < lhs);}inline bool operator==(const TypeInfoProxy & lhs, const TypeInfoProxy & rhs) {  return ((*lhs.ti) == (*rhs.ti)) != 0;}inline bool operator!=(const TypeInfoProxy & lhs, const TypeInfoProxy & rhs) {  return ((*lhs.ti) != (*rhs.ti)) != 0;}  struct Widget {  virtual ~Widget() {}  virtual void print_me(void) = 0;};struct WidgetRegistrar {  void register_widget(Widget * widget) {    widget_map[&typeid(*widget)] = widget;  }  template <typename T>  T * get_widget(void) {    return &dynamic_cast<T &>(*widget_map[&typeid(T)]);  }  template <typename T>  void unregister_widget(void) {    widget_map.erase(&typeid(T));  }  typedef std::map<TypeInfoProxy, Widget *> WidgetMap;  WidgetMap widget_map;};struct A : Widget {  void print_me(void) { std::cout << "I'm an A!" << std::endl; }};struct B : Widget {  void print_me(void) { std::cout << "I'm a B!" << std::endl; }};int main(int, char **) {  WidgetRegistrar registrar;  A a;  B b;  registrar.register_widget(&a);  registrar.register_widget(&b);  A * pa = registrar.get_widget<A>();  pa->print_me();  B * pb = registrar.get_widget<B>();  pb->print_me();  registrar.unregister_widget<A>();  registrar.unregister_widget<B>();    std::cout << registrar.widget_map.size();    return 0;}
Quote:Original post by aclysma
I think the best solution would be some construct in the declaration like
template<class T : public Widget> ...

Is there a language construct like this that I can use?

Not yet. The next C++ standard, C++0x, will feature "concepts", which are basically restrictions on template arguments. With concepts, you can specify that a particular template must be derived from a particular base class.
NextWar: The Quest for Earth available now for Windows Phone 7.
Why are you even using templates here? Why not just pass a widget pointer:

struct WidgetRegister{  void registerWidget(Widget *widget);  void registerWidget(WidgetRegister *widget);  Widget *getWidget();  void unregisterWidget();}
1)
template<typename T> typename boost::enable_if< boost::is_base_of< Widget , T > >::type foo(T* widget);
3) This isn't probably isn't worth the hassle and requires fairly significant and intrusive changes but here you go.

// Warning: Untested, the basic idea should work but theres probably some// bugs that need sorting out.// A small utilitytemplate<typename T>struct dummy { };namespace sfinae{    typedef char one;    typedef struct { char v[2]; } two;};// Use sfinae to check for a static T::getName with the correct signaturetemplate<typename T>struct has_getName{    template<std::string (*)(dummy<T>)>    struct wrapper { };    template<typename T>    static sfinae::one test(wrapper<*T::getName>*);    template<typename T>    static sfinae::two test(...);    enum { value = sizeof(test<T>(0)) == sizeof(sfinae::one) };};// widget has to be a CRTP class to perform the test.template<typename Derived>struct widget{    // Check that Derived has a getName member with the right signature    BOOST_MPL_ASSERT_MSG((has_getName<Derived>::value), WIDGET_MUST_OVERRIDE_getName, (types<Base>));    // For a given widget Derived getName must have signature     // std::string (dummy<Derived>) so that we can distinguish the getName    // in derived from the one in the base class. We give it a default value    // so that the caller doesn't have to know about it.    static std::string getName(dummy<widget> = dummy<widget>()) { return "widget"; }};// extends<Derived, Base> is used to inherit from a widget other then widget<Derived>// This is the simplest possible implimentation and assumes widget<T>// does nothing but the getName check, it's possible to write extends so// this isn't the case and so that it avoids the multiple inheritance// but thats left as an exercise for the reader.template<typename Derived, Base>struct extends : Base , widget<Derived>{ };    // Worksstruct my_widget : widget<my_widget>{    static std::string getName(dummy<my_widget> = dummy<my_widget>())    { return "my_widget"; }};// Error: WIDGET_MUST_OVERRIDE_getName with T0 = error_widgetstruct error_widget : widget<error_widget>{};// Worksstruct my_derived_widget : extends< my_derived_widget , my_widget >{    static std::string getName(dummy<my_derived_widget> = dummy<my_derived_widget>())    { return "my_derived_widget"; }};// Error: WIDGET_MUST_OVERRIDE_getName with T0 = my_derived_error_widgetstruct my_derived_error_widget : extends< my_derived_error_widget , my_widget >{};


Personally I'd try and avoid the whole getName thing using something like this:
struct WidgetRegister{      WidgetRegister() : widget_register_id(next_widget_register_id)      { ++next_widget_register_id; }      template<class T>      void registerWidget(T *widget) { storage<T>::values[widget_register_id] = widget; }      template<class T>      T* getWidget() { return storage<T>::values[widget_register_id]; };      template<class T>      void unregisterWidget() { storage<T>::values.erase(widget_register_id); }private:    static std::size_t next_widget_register_id;    std::size_t widget_register_id;    template<typename T>    struct storage    {        static std::map<std::size_t, T*> values;    };};template<typename T>std::map<std::size_t, T*> WidgetRegister::storage<T>::values;std::size_t WidgetRegister::next_widget_register_id;
Wow, some great responses here. Thanks!

SiCrane: Thanks for the info on RTTI and pointing out a way to enforce explicit template argument specification.

ToohrVyk/Julian90: SFINAE is totally new to me and I definately learned something today. Pretty cool trick!

King Mir: While your method is certainly straightforward, it is not typesafe as it requires the user of the register class to upcast, which is exactly what I'm trying to avoid.

For now, I think I'm going to go with SiCrane's approach as the cost of using RTTI looks very minimal (small increase in executable size). Using RTTI, I will no longer need to implement any getName() functions as RTTI will provide this. Not needing a getName() function solves 1, 2, and 3.

Should RTTI not work for me (either the cost is unacceptable or more likely the VC++8 compiler's type name is unpredictable) you can bet I'll be back to this post to dig up a SFINAE approach which while complex, should also solve this problem.

Thanks again for the excellent responses and in particular SiCrane and Julian for writing up such detailed responses.
In MSVC, if you have exception handling enabled (which you should if you use any standard library classes), then the additional overhead of RTTI is very minimal, since the exception handling functionality in MSVC uses RTTI data structures.
Quote:Original post by aclysma
King Mir: While your method is certainly straightforward, it is not typesafe as it requires the user of the register class to upcast, which is exactly what I'm trying to avoid.
Only with getWidget(), so there is no reason to make the other methods templates. Furthermore, using a template here does not eliminate the casting, it just moves it inside the getWidget function. The same problems of ensuring the correct type of widget is used still exist.

Quote:Original post by King Mir
Only with getWidget(), so there is no reason to make the other methods templates. Furthermore, using a template here does not eliminate the casting, it just moves it inside the getWidget function. The same problems of ensuring the correct type of widget is used still exist.


Good point about the templates being unnecessary for all functions but the get. (assuming typeid() works on variables as well as the type name itself, and that it always returns the most extended class). I will look into this.

As to typesafety of the get, you're exactly right - the cast is still there and I'm just hiding it. But I'm banking on the fact that T::getName() or the RTTI type_info::name() function returns the same thing all the time and is unique per class. If this is the case, I believe the upcast is provably safe because the only way for the widget to be found would be to have registered in the map with what T::getName() or type_info::name() returned.

This topic is closed to new replies.

Advertisement