简体   繁体   中英

Class template, member function definition if object is of type X?

Is it possible to create a class template with a member function definition only if the object created is of a specific type?

I've created a template class I will use for storing either int or doubles, but for doubles I would like to be able to set precision too (objects created with myclass < double> should have this functionality, but for myclass< int> there is no need for that to be present at all).

I know I can use a base class template, and create new classes "myInt", "myDouble" using that and implement the functionality only in the myDouble class, but I think it would be cleaner to define the functionality (both the function and a member variable) for doubles in the class template, if that's possible and preferable?

Let's add an example to show what I want to do:

#include <iostream>
#include <iomanip>

class commonBase{

public:
    void setState(int state);
    virtual void print() = 0;
private:
    int _my_state;
};


template <typename T>
class generalObject : public commonBase {
public:
    void value(T value);
    void print(){ std::cout << "My value: " << _my_value << std::endl; }
private:
    T _my_value;
};


template <typename T>
void generalObject<T>::value(T value){
    _my_value = value;
}

// Is there any way do specialize only only whats different from the generalObject template?
// Here I thought I could specialize the case where a generalObject is created of <double>, but
// when I do, nothing is derived from generalObject (or at least not visible as far as I can tell)
template<>
class generalObject<double>{
public:
    void setPrecision(int precision){ _my_precision = precision; }

    // here I would like a special implementation of print(), which overrides the print() in generalObject 
    // and instead also prints according to the precision set when the object is of <double> type.
    // Row below an example which doesn't work (compiler error, _my_value undefined)
    void print(){ std::cout << "My value: " << std::setprecision(_my_precision) << _my_value << std::endl; }

private:
    int _my_precision;
};


int main(int argc, char* argv[]){

    generalObject<int> o1;
    o1.value(1);
    o1.print();
    o1.setState(1); //inherited from the commonBase

    generalObject<double> o2;
    o2.setPrecision(2);
    o2.value(2); //here value isn't available (compile error)
    o2.print();
    o2.setState(123); //also isn't available (compile error)


}

Sure.

template <typename T> class Poly;
void set_precision(Poly<double>* self, int a) {};

If you really want dot notation you can then add:

template <typename T> class Poly {
  public: void set_precision(int a){::set_precision(this,a);}
  ...

However I think you should think about what you're trying to accomplish. If MyInt and MyDouble have different fields and different methods and different implementations, they should probably be different classes.

This can be solved using template specialization .

We first define a common template...

template< typename T >
struct myclass
{
    // common stuff   
};

... and specialize that for double :

template<>
struct myclass<double>
{
    int precision = 10;
    void setprecision( int p ){ precision = p; }
};

Now the setprecision() method can only be called for myclass<double> . The compiler will complain if we try to call it for anything else, like myclass<int> .

int main()
{    
    myclass<double> d;
    d.setprecision( 42 );    // compiles

    myclass<int> i;
    i.setprecision( 42 );    // fails to compile, as expected
}

Demo.

If the question is about a member function, then here is one of the ways to do it without class template specialization:

#include <iostream>
#include <type_traits>

template <typename T>
struct Type {
  template <typename U = T,
            typename = typename std::enable_if<std::is_same<U, double>::value>::type>
  void only_for_double() {
    std::cout << "a doubling" << std::endl;
  }  
};

int main() {
  Type<int> n;
  Type<double> d;

// n.only_for_double(); // does not compile.
  d.only_for_double();
}

Example on ideone.com

If you require a data-member presence based on the template parameter, you will have to do some kind of specialization, in which case it is, probably, simpler to put the function into corresponding specialization.

EDIT: After OP made his question more specific Here is one way to do it without extra class and getting rid of virtual functions. Hope it helps.

#include <iostream>
#include <iomanip>

template <typename T, typename Derived = void>
class commonBase {
  public:
    void setState(int state) { 
      _my_state = state;
    }
    void value(T value) {
      _my_value = value;
    }

    template <typename U = Derived,
              typename std::enable_if<std::is_same<U, void>::value,
                                      void * >::type = nullptr>
    void print() const {
      std::cout << "My value: " << _my_value << std::endl;
    }

  template <typename U = Derived,
            typename std::enable_if<!std::is_same<U, void>::value,
                                      void * >::type = nullptr>
    void print() const {
      static_cast<Derived const *>(this)->_print();
    }
  protected:
    T _my_value;
    int _my_state;
};

template <typename T>
class generalObject : public commonBase<T> {
};

template<>
class generalObject<double> : public commonBase<double, generalObject<double>> {
  private:
    friend commonBase<double, generalObject<double>>;

    void _print() const { 
      std::cout << "My value: " << std::setprecision(_my_precision) << 
                   _my_value << std::endl;
    }

  public:
    void setPrecision(int precision){ _my_precision = precision; }

private:
    int _my_precision;
};


int main(){
  generalObject<int> o1;
  o1.value(1);
  o1.print();
  o1.setState(1); 

  generalObject<double> o2;
  o2.setPrecision(2);
  o2.value(1.234);
  o2.print();
  o2.setState(123);
}

Same code on ideone.com

The basic way to have a member function of a class template exist only for some template parameters is to create a specialization of the class template for those template parameters.

template<typename T>class X{
    // general definition
};

template<>class X<double>{
    // double-specific definition
};

The downside of this is that the specialization will need to duplicate anything that is common. One way to address this is to move the common things out to a base class template:

template<typename T>class Xcommon{
    // common stuff
};
template<typename T>class X: public Xcommon<T>{
    // general definition
};

template<>class X<double>: public Xcommon<double>{
    // double-specific definition
};

Alternatively, you can do it the other way: put the common stuff in the derived class, and the extras in the base, and specialize the base:

template<typename T>class Xextras{
    // empty by default
};
template<typename T>class X: public Xextras<T>{
    // common definition
};

template<>class Xextras<double>{
    // double-specific definition
};

Either way can work; which is better depends on the details.

Both these methods work for data members and member functions.

Alternatively, you can use enable_if to mean that member functions are not selected by overload resolution if the template parameter doesn't meet a required condition. This requires that the member function is itself a template.

template<typename T>class X{
    template<typename U=T> // make it a template,
    std::enable_if<std::is_same_v<U,double>> double_specific_function(){
        // do stuff
    }
};

I wouldn't recommend this option unless there is no other choice.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM