문제

I wanted to write some string wrappers that will accept a string if it is valid for their type:

  • Length valid strings: mm, m, ft, in
  • Angle valid strings: deg, rad

I imagined a use like:

Length len = read_from_keyboard(); // or some means of initialization
if( len.is_valid() )   { ...   }

So I wrote these implementations.

struct Length
{
  QString m;

  Length() {}

  Length( QString s )   {   if( is_valid_string(s) )  {  m = s; }      }

  QString operator() () {   return m;      }

  bool is_valid()       {   return is_valid_string(m);      }

  static bool is_valid_string( QString s ) { 
    return s == "mm" || s=="m" || s=="ft" || s=="in";
  }
};

and

struct Angle{
  QString m;

  Angle() {}

  Angle( QString s )    {   if( is_valid_string(s) )  {  m = s; }      }

  QString operator() () {   return m;      }

  bool is_valid()       {   return is_valid_string(m);      }

  static bool is_valid_string( QString s ) { 
    return s == "deg" || s=="rad";
  }
};

Which seems to me some form static-polymorphism, with is_valid_string() as their only difference in implementation.

Since I have a number of these classes, I thought of using static inheritance (not via virtual) to grasp the common functionality.

So, I thought of using the curiously recurring template pattern:

template <class T>
struct ConstrainedText {
  QString m;

  ConstrainedText() {}

  ConstrainedText( QString s ) {   if( T::is_valid_string(s) )   {  m = s;  }    }

  QString operator() ()        {   return m;      }

  bool is_valid()              {   return T::is_valid_string(m);    }

};


struct Angle : public ConstrainedText<Angle> {
   static bool is_valid_string( QString s ) { 
      return s == "deg" || s="rad";
   }  
};


struct Length : public ConstrainedText<Angle> {
   static bool is_valid_string( QString s ) { 
      return s == "mm" || s="m" || s=="ft" || s=="in";
   }  
};

but now I've lost the implicit constructors inside the base-class and I have to rewrite them!

Is there any other way I could implement this in order to have the same interface [default constructor, implicit constructor and is_value() ] and only write minimal code for the different part (the static is_valid_string() )?

I know I could have used the preprocessor, but I want the code to be debugger-friendly.

도움이 되었습니까?

해결책

As others have noted, since constructor are not inherited, you will have to redefine them. You could however do something like this, code at ideone.com:

#include <string>
#include <stdexcept>
#include <iostream>

template <class T>        
class ConstrainedText {        
  std::string m;                 

protected:
  ConstrainedText() {}
  ~ConstrainedText() {}
public: 
  bool is_valid() {        
    return T::is_valid_string(m);        
  }        

  static T Create(std::string const & s)
  {
      if (T::is_valid_string(s)) {
          T t;
          static_cast<ConstrainedText<T>&>(t).m = s;
          return t;
      }

      throw std::runtime_error("invalid input!");
  }
};        

struct Angle : public ConstrainedText<Angle> {        
   static bool is_valid_string( std::string s ) {         
      return s == "deg" || s=="rad";        
   }          
};        


struct Length : public ConstrainedText<Length> {        
   static bool is_valid_string( std::string s ) {         
      return s == "mm" || s == "m" || s == "ft" || s == "in";        
   }          
};        

int main()
{
   auto a = Angle::Create("deg");
   auto l = Length::Create("mm");

   try {
       Angle::Create("bimbo");
   } catch (std::runtime_error & pEx) {
       std::cout << "exception as expected" << std::endl;
   }

   try {
       Length::Create("bimbo");
   } catch (std::runtime_error & pEx) {
       std::cout << "exception as expected" << std::endl;
   }
}

다른 팁

Constructors are not inherited, and you can't inherit them with using.

In C++11 you can use variadic templates and perfect forwarding:

template<typename... Args> Derived(Args &&...args):
    Base(std::forward<Args>(args)...) {}

Since constructors are never inherited in C++ (other than default if no constructor is defined) you're stuck with either implementing them again yourself or templating (with or without the C++11 variadic templates) to make the compiler match the constructor. Note however that doing so makes documentation and compiler errors more difficult to interpret because the meaningless template constructors add a (sometimes) confusing layer of abstraction.

Preprocessor operators can also get around this, but I would only do so if the constructor is very simple and easy to replicate across all inheritors.

So in general the answer to if you can cleanly generate an inherited default constructor and an implicit constructor automatically is no. But the two approaches outlined above would do it in a somewhat messy but automatic manner.

I used a Policy and it saved the day. Now I don't have to rewrite the constructors, and I only need to implement the is_valid_string(). It is now C++98 compliant, too.

template <class Policy>
struct ConstrainedText {
  QString m;

  ConstrainedText() {}

  ConstrainedText( QString s ) {   if( Policy::is_valid_string(s) )   {  m = s;  }    }

  QString operator() ()        {   return m;      }

  bool is_valid()              {   return Policy::is_valid_string(m);    }

};


struct Angle_policy {
   static bool is_valid_string( QString s ) { 
      return s == "deg" || s="rad";
   }  
};

struct Length_policy {
   static bool is_valid_string( QString s ) { 
      return s == "mm" || s="m" || s=="ft" || s=="in";
   }  
};

typedef ConstrainedText<Length_policy> Length;
typedef ConstrainedText<Angle_policy>  Angle;

Thank you all for your help

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top