28

I have a templatized class like so :

template<typename T>
class A
{
    protected:
    std::vector<T> myVector;

    public:
    /*
    constructors + a bunch of member functions here
    */
}

I would like to add just ONE member function that would work only for 1 given type of T. Is it possible to do that at all without having to specialize the class and reimplement all the other already existing methods?

Thanks

bob kaggle
  • 431
  • 5
  • 8

6 Answers6

21

The simplest and cleanest solution is to use a static_assert() in the body of a method, rejecting other types than the selected one (in the below example only integers are accepted):

#include <type_traits>  
#include <vector>

template <typename T>
class A
{
public:
    void onlyForInts(T t)
    {
        static_assert(std::is_same<T, int>::value, "Works only with ints!");
    }

protected:
    std::vector<T> myVector;
};

int main()
{
    A<int> i;
    i.onlyForInts(1); // works !

    A<float> f;
    //f.onlyForInts(3.14f); // does not compile !
}

OK CASE DEMO NOK CASE DEMO

This utilizes the fact that a compiler instantiates a member function of a class template only when one is actually used (not when the class template is instantiated itself). And with the above solution, when a compiler tries to do so, it fails due to the execution of a static_assert.

C++ Standard Reference:

§ 14.7.1 Implicit instantiation [temp.inst]

  1. Unless a function template specialization has been explicitly instantiated or explicitly specialized, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist. Unless a call is to a function template explicit specialization or to a member function of an explicitly specialized class template, a default argument for a function template or a member function of a class template is implicitly instantiated when the function is called in a context that requires the value of the default argument.

  2. [ Example:

    template<class T> struct Z {
      void f();
      void g();
    };
    
    void h() {
      Z<int> a;     // instantiation of class Z<int> required
      Z<char>* p;   // instantiation of class Z<char> not required
      Z<double>* q; // instantiation of class Z<double> not required
      a.f();        // instantiation of Z<int>::f() required
      p->g();       // instantiation of class Z<char> required, and
                    // instantiation of Z<char>::g() required
    }
    

    Nothing in this example requires class Z<double>, Z<int>::g(), or Z<char>::f() to be implicitly instantiated. — end example ]

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • It's debatable how clean this solution really is since it allows to take the functions address even if one isn't allowed to call it. – Fytch Aug 25 '14 at 13:15
  • 1
    @Asfdlol: [no it doesn't](http://ideone.com/rtNaKE). That requires the function to be instantiated, which the compiler can't do. – Cornstalks Aug 25 '14 at 13:19
  • @Asfdlol: and what if you want to allow two, three, od N types ? compare my solution (adding alternative to one readable condition) VS. your solution (not possible at all ?). – Piotr Skotnicki Aug 25 '14 at 13:20
  • Thanks Piotr. This looks indeed as a very straightforward and scalable solution for my problem. – bob kaggle Aug 25 '14 at 13:26
  • 1
    the `static_assert` gives you a nice error message, but it's also nice to know that you can specialize a single member function without code duplication. Just give the generic `onlyForInts()` an empty no-op implementation, and specialize it out-of-class for `int`: http://coliru.stacked-crooked.com/a/2f790496fb1b3cf3 – TemplateRex Aug 25 '14 at 16:28
13

Yes, it's possible in C++03 with CRTP (Curiously recurring template pattern):

#include <numeric>
#include <vector>

template<typename Derived, typename T>
struct Base
{
};

template<typename Derived>
struct Base<Derived, int>
{
    int Sum() const
    {
        return std::accumulate(static_cast<Derived const*>(this)->myVector.begin(), static_cast<Derived const*>(this)->myVector.end(), int());
    }
};

template<typename T>
class A : public Base<A<T>, T>
{
    friend class Base<A<T>, T>;

protected:
    std::vector<T> myVector;

public:
    /*
    constructors + a bunch of member functions here
    */
};

int main()
{
    A<int> Foo;
    Foo.Sum();
}
Fytch
  • 1,067
  • 1
  • 6
  • 18
5

As an alternative solution, which works also in plain C++03 (as opposed to static_assert or enable_if solutions), you may add extra defaulted template argument which will let you have both specialized and unspecialized version of class. Then you can inherit your specialized version from the unspecialized one.

Here is a sample snippet:

#include <vector>

template<typename T, bool unspecialized = false>
class A
{
  protected:
    std::vector<T> myVector;

  public:
    void setVec(const std::vector<T>& vec) { myVector = vec; }
    /*
    constructors + a bunch of member functions here
    */
};

template<>
class A<int, false> : public A<int, true>
{
  public: 
   int onlyForInt() {
      return 25;
   }
};

int main() {
  // your code goes here
  std::vector<int> vec;
  A<int> a;
  a.setVec(vec);
  a.onlyForInt();
  return 0;
}

The drawbacks of this solution is the need to add constructor forwarders, if class has non-trivial constructors.

Rapptz
  • 20,807
  • 5
  • 72
  • 86
Krizz
  • 11,362
  • 1
  • 30
  • 43
  • 2
    You'll have to implement all the constructors twice in C++03 (since you can't inherit constructors) and you also have to write a lot of boilerplate code for the operators due to the auxiliary template parameter. – Fytch Aug 25 '14 at 12:51
  • @Asfdlol: yes, that's correct. That is a disadvantage of this approach. – Krizz Aug 25 '14 at 12:54
  • @pqnet: I haven't said it would not. – Krizz Aug 25 '14 at 13:02
4

The static_assert technique by @PiotrS. works nicely. But it's also nice to know that you can specialize a single member function without code duplication. Just give the generic onlyForInts() an empty no-op implementation, and specialize it out-of-class for int

#include <vector>

template <typename T>
class A
{
public:
    void onlyForInts(T t)
    {
        // no-op
    }

protected:
    std::vector<T> myVector;
};

template<>
void A<int>::onlyForInts(int t)
{
    // works  
}

int main()
{
    A<int> i;
    i.onlyForInts(1); // works !

    A<float> f;
    f.onlyForInts(3.14f); // compiles, but does nothing !
}

Live Example.

This technique comes in handy if you want to have int specific behavior without completely disabling the generic behavior.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • +1 I like this approach, but it lets the programmer do something very counter-intuitive and should probably be handled with some form of SFINAE rather than letting them. – RamblingMad Aug 26 '14 at 01:07
4

One approach not given yet in the answers is using the standard library std::enable_if to perform SFINAE on a base class that you inherit to the main class that defines appropriate member functions.

Example code:

template<typename T, class Enable = void>
class A_base;

template<typename T>
class A_base<T, typename std::enable_if<std::is_integral<T>::value>::type>{
    public:
        void only_for_ints(){/* integer-based function */}
};

template<typename T>
class A_base<T, typename std::enable_if<!std::is_integral<T>::value>::type>{
    public:
        // maybe specialize for non-int
};

template<typename T>
class A: public A_base<T>{
    protected:
        std::vector<T> my_vector;
};

This approach would be better than an empty function because you are being more strict about your API and better than a static_cast because it simply won't make it to the inside of the function (it won't exist) and will give you a nice error message at compile time (GCC shows "has no member named ‘only_for_ints’" on my machine).

The downside to this method would be compile time and code bloat, but I don't think it's too hefty.

(don't you dare say that C++11 requirement is a down-side, we're in 2014 god-damnit and the next standard has even be finalized already!)

Also, I noticed, you will probably have to define my_vector in the base class instead of the final because you probably want to handle that data within the member function.

A nice way to do that without duplicating a bunch of code is to create a base base class (good god) and inherit that class in the base class.

Example:

template<typename T>
class base_data{
    protected:
        std::vector<T> my_vector;
};

template<typename T>
class A_base<T, typename std::enable_if<std::is_integral<T>::value>::type>: public base_bata<T>{
    public:
        void only_for_ints(){/* phew, finally. fiddle around with my_vector! */}
};

// non-integer A-base

template<typename T>
class A: public A_base<T>{
    protected:
        // helper functions not available in base
};

That does leave a horrible looking multiple-inheritance scheme, but it is very workable and makes it easy to define members based on template parameters (for future proofing).

People often don't like multiple-inheritance or how complicated/messy SFINAE looks, but I couldn't live without it now that I know of it: the speed of static code with the polymorphism of dynamic code!

RamblingMad
  • 5,332
  • 2
  • 24
  • 48
2

Not sure where I found this, but you can use = delete; as the function definition inside the class, thereby deleting the function for the general case, and then explicitly specialize outside the class:

template <typename T>
struct A
{
  auto int_only(T) -> void = delete;
};

template <> auto A<int>::int_only(int) -> void {}

int main()
{
  auto a_int = A<int>{};
  auto a_dbl = A<double>{};
  a_int.int_only(0);
  // a_dbl.int_only(3.14);  error: call to deleted member function
}

https://en.cppreference.com/w/cpp/language/function#Deleted_functions