Is there anyway I can specialize JUST the Contains function of the class below?

I tried for hours and cannot figure it out as I'm not too good at specialization but I learned templates recently.

I want to do something like:

bool Contains(string ToFind)
            {
                //If typeof(T) == typeof(string) or typeID or typeName.. then execute something different..
                for (unsigned I = 0; I < TypeData.size(); I++)
                {
                    if (TypeData[I] == ToFind)
                        return true;
                }
                return false;
            }

The Templated Class containing the "Contains" function..

#ifndef TEMPLATES_H_INCLUDED
#define TEMPLATES_H_INCLUDED
#include <iostream>
#include <vector>

using namespace std;

template <typename T>
class CustomType
{
    private:
        vector<T> TypeData;
        void Insert() {}
        template<typename A, typename... Args>
        CustomType& Insert(A FirstArg, const Args... RemainingArgs)
        {
            this->TypeData.push_back(FirstArg);
            Insert(RemainingArgs...);
            return *this;
        }

    public:
        CustomType() {}
        template<typename A, typename... Args>
        CustomType(A FirstArg, const Args... RemainingArgs)
        {
            this->TypeData.push_back(FirstArg);
            Insert(RemainingArgs...);
        }
        ~CustomType() {}

        int size() {return TypeData.size();}
        
        //Other functions/overloads removed at the moment.. just for this post.

        bool Contains(T ToFind)
        {
            for (unsigned I = 0; I < TypeData.size(); I++)
            {
                if (TypeData[I] == ToFind)
                    return true;
            }
            return false;
        }
};

#endif // TEMPLATES_H_INCLUDED

Recommended Answers

All 3 Replies

Well, one way to do this is with a friend function:

#ifndef TEMPLATES_H_INCLUDED
#define TEMPLATES_H_INCLUDED
#include <iostream>
#include <vector>

using namespace std;

template <typename T>
class CustomType
{
    private:
        vector<T> TypeData;
        void Insert() {}
        template<typename A, typename... Args>
        CustomType& Insert(A FirstArg, const Args... RemainingArgs)
        {
            this->TypeData.push_back(FirstArg);
            Insert(RemainingArgs...);
            return *this;
        }

    public:
        CustomType() {}
        template<typename A, typename... Args>
        CustomType(A FirstArg, const Args... RemainingArgs)
        {
            this->TypeData.push_back(FirstArg);
            Insert(RemainingArgs...);
        }
        ~CustomType() {}

        int size() {return TypeData.size();}
        
        //Other functions/overloads removed at the moment.. just for this post.
        
        friend bool Contains_impl(const CustomType<string>& aThis, const string& ToFind);

        template <typename U>
        friend bool Contains_impl(const CustomType<U>& aThis, const U& ToFind);
        
        bool Contains(const T& ToFind) const {
          Contains_impl(*this, ToFind);
        };
};

inline
bool Contains_impl(const CustomType<string>& aThis, const string& ToFind)
{
  // do the extra stuff here.

  for (unsigned I = 0; I < aThis.TypeData.size(); I++)
  {
    if (aThis.TypeData[I] == ToFind)
      return true;
  }
  return false;
}

template <typename T>
bool Contains_impl(const CustomType<T>& aThis, const T& ToFind)
{
  for (unsigned I = 0; I < aThis.TypeData.size(); I++)
  {
    if (aThis.TypeData[I] == ToFind)
      return true;
  }
  return false;
}

#endif // TEMPLATES_H_INCLUDED

When it comes to function templates or member functions of class templates, don't specialize, overload instead!

If you have many functions in a class that should have this same pattern, then you might consider either specializing the entire class template, or creating a helper class template that is specialized (and contains only those functions that need to be specialized).

Also, these types of patterns are often implemented with a Policy template argument. Policies are a kind of helper class that can be attached to a class template to perform some extra operations at specific points. If you find that the special work that needs to be done for some types is somewhat redundant or appears in several functions, it might be worth lumping this special work into a Policy argument (note that an "empty" or no-op policy has no overhead).

I meant something like:

bool Contains(T& ToFind)
        {
            for (unsigned I = 0; I < TypeData.size(); I++)
            {
                if (TypeData[I] == ToFind)
                    return true;
            }
            return false;
        }

        bool Contains(string StringToFind, bool CaseSensitive = true)
        {
            vector<string> Temp = TypeData;     //Create  temporary type so the original doesn't change.
            if (!CaseSensitive)
            {
                for (unsigned short I = 0; I < StringToFind.length(); I++)
                    StringToFind[I] = tolower(StringToFind[I]);

                for (unsigned short I = 0; I < Temp.size(); I++)
                    for (unsigned short J = 0; J < Temp[I].size(); J++)
                        Temp[I][J] = tolower(Temp[I][J]);
            }
            for (unsigned I = 0; I < Temp.size(); I++)
            {
                if (Temp[I] == StringToFind)
                    return true;
            }
            return false;
        }

See in the code above, I have different parameters for the Contains Functions.. One if of unknown type.. BUT if the type is a string, then use the StringContains function instead of the TContains function.

That's easy enough, all you need is to construct a predicate functor (and btw, that copying to temporary and turning the entire array into lower case is just incredibly wasteful).

What about this:

template <typename Predicate>
        bool Contains(Predicate pred) const
        {
            for (auto it = TypeData.cbegin(); it != TypeData.cend(); ++it)
            {
                if (pred(*it))  // evaluate predicate.
                    return true;
            }
            return false;
        }

        bool Contains(const T& ToFind) const // don't forget const!!!
        {
            return Contains([&ToFind](const T& rhs) { return (rhs == ToFind); });
        }

Now, all you need is a string version of this predicate that is case-insensitive.

First of all, when you get into the business of specializing templates for standard STL containers (like string) you should keep in mind that std::string or std::vector<T> or others are not as simple as they appear, because in reality they are, respectively, std::basic_string< C, CharTraits, Allocator> and std::vector<T, Allocator> . So, you should normally specify all template arguments in your specializations, otherwise you might get surprises later.

So, here is a simple predicate functor for case-insensitive comparisons:

// General template for non-strings.
template <typename T>
struct CaseInsensitiveCompare { 
  char cannot_create_a_case_insensitive_comparison_for_non_string_type[0]; // this will throw the compiler on a fit of rage!
};

// Specialize for strings:
template <typename C, typename CharT, typename Allocator>
struct CaseInsensitiveCompare< std::basic_string<C, CharT, Allocator> > {
  typedef std::basic_string<C, CharT, Allocator> argument_type;
  typedef bool result_type;
  
  argument_type lhs;
  CaseInsensitiveCompare(const argument_type& aLhs) : lhs(aLhs) {
    using std::tolower; //include standard tolower in ADL.
    for(auto it = lhs.begin(); it != lhs.end(); ++it)
      CharT::assign(*it, tolower(*it));
  };

  result_type operator()(const argument_type& rhs) const {
    using std::tolower; //include standard tolower in ADL.
    if( rhs.size() != lhs.size() )
      return false;
    for(std::size_t i = 0; i < lhs.size(); ++i) {
      if( ! CharT::eq( lhs[i], tolower(rhs[i]) ) )
        return false;
    };
    return true;
  }; 
};

// And create a nice convenient factory function:
template <typename T>
CaseInsensitiveCompare<T> CaseInsensitive(const T& value) {
  return CaseInsensitiveCompare<T>(value);
};

Now, with the above, you can do:

int main() {
  CustomType<int> i_obj;
  // ..
  if( i_obj.Contains(42) )  // will call Contains(T)
    std::cout << "i_obj contains value 42!" << std::endl;
  
  if( i_obj.Contains( CaseInsensitive(42) ) )  // ERROR: a big WTF from the compiler.
    std::cout << "i_obj contains value case-insensitive 42!" << std::endl;
  
  CustomType<std::string> s_obj;
  // ..

  if( s_obj.Contains( "Foo" ) )  // will call Contains(T)
    std::cout << "s_obj contains value 'Foo'!" << std::endl;

  if( s_obj.Contains( CaseInsensitive( std::string("Foo") ) )  // will call Contains(Predicate) with predicate being CaseInsensitiveCompare< std::string >.
    std::cout << "s_obj contains value case-insensitive 'Foo'!" << std::endl;

  return 0;
};

The lesson to learn from all this is that you must not aim at doing things differently based on types, you must look at how to handle any type in the same way (like the Contains function works on any predicate) and then put the type-specific code where it belongs (as a predicate, policy, visitor, or whatever else that injects external code into the algorithm). In other words, try to extract the essence of the algorithm (which is basically a linear-search in this case), and identify the things that are external to that algorithm (in this case, the details of the comparison or predicate). Doing it this way ensures that your algorithm are a generic as can be, and that injecting type-specific code or behavior-changing policies is very easy to do (like above).

commented: I understand partially but thanks.. I'll look into it more to fully understand. +6
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.