Search code examples
c++c++11templatestemplate-specializationstdstring

Template specialization by Container::value_type


I have the following two functions that I use to convert std containers to strings for logging purposes.

template <typename TCollection, std::string(*ToStringFunc)(typename TCollection::value_type)>
std::string LOG_COLLECTION(const TCollection& collection)
{
   std::string as_str;
   for (const auto& element : collection) 
   {
      as_str += ToStringFunc(element) + " ";
   }
   return as_str;
}

template <typename TCollection>
std::string LOG_COLLECTION(const TCollection& collection) 
{
   return LOG_COLLECTION<TCollection, std::to_string>(collection);
}

I want a specialization for containers of std::strings. Something like the following:

template <typename TCollection<std::string>>
std::string LOG_COLLECTION(const TCollection<std::string>& collection)
{
   std::string as_str;
   for (const auto& element : collection) 
   {
      as_str += ToStringFunc(element) + " ";
   }
   return as_str;
}

int main()
{
   std::vector<std::string> vec{ "a", "b", "c" };
   std::string as_str = LOG_COLLECTION(vec)
}

How do I do that (I am using c++11)? I tried to search the web but did not find a solution.


Solution

  • Solution - I: Using template template arguments

    Like @Scheff suggested in the comments, you can provide a template template function specialization for std::strings.

    Following is an example code: (See Online Live)

    #include <iostream>
    #include <vector>
    #include <string>
    
    // template alias for function pointer
    template<template<class...> class TCollection, typename ValueType>
    using FunctionPtrType = std::string(*)(typename TCollection<ValueType>::value_type);
    
    // `LOG_COLLECTION` for the  Container<non-std::string>
    template <template<class...> class TCollection, typename ValueType>
    std::string LOG_COLLECTION(const TCollection<ValueType>& collection,
       FunctionPtrType<TCollection, ValueType> ToStringFunc)
    {
       std::string as_str;
       for (const ValueType element : collection) {
          as_str += ToStringFunc(element) + " ";
       }
       return as_str;
    }
    
    // `LOG_COLLECTION` for the Container<std::string>
    template <template<class...> class TCollection, typename... Args>
    std::string LOG_COLLECTION(const TCollection<std::string, Args...>& collection)
    {
       std::string as_str;
       for (const auto& element : collection) {
          as_str += (element + " ");
       }
       return as_str;
    }
    
    int main()
    {
       std::vector<int> vec{ 1, 2, 3 };
       // call the Container<non-std::string> like
       std::string as_str = LOG_COLLECTION(vec,
                       [](int val) { return ::std::to_string(val); });    
    
       std::vector<std::string> vec2{ "1", "2", "3" };
       std::string as_str2 = LOG_COLLECTION(vec2); // call with no `ToString` function!     
    }
    

    Solution - II: Using SFINAE

    Alternatively SFINAE ("Substitution Failure Is Not An Error") the LOG_COLLECTION function as follows:

    (See Online Live)

    #include <type_traits> // std::integral_constant, std::is_same, std::enable_if
    
    // traits for checking the `value_type == std::string`
    template<class Container> struct has_std_string_value_type final
       : public std::integral_constant<bool, std::is_same<std::string, typename Container::value_type>::value>
    {};
    
    // template alias for function pointer
    template<typename TCollection>
    using FunctionPtrType = std::string(*)(typename TCollection::value_type);
    
    // for the Container<non-std::string>
    template <typename TCollection>
    auto LOG_COLLECTION(const TCollection& collection, FunctionPtrType<TCollection> ToStringFunc)
       -> typename std::enable_if<! has_std_string_value_type<TCollection>::value, std::string>::type
    {
       std::string as_str;
       for (const auto element : collection) {
          as_str += ToStringFunc(element) + " ";
       }
       return as_str;
    }
    
    // for the Container<std::string>
    template <typename TCollection>
    auto LOG_COLLECTION(const TCollection& collection)
       -> typename std::enable_if<has_std_string_value_type<TCollection>::value, std::string>::type
    {
       std::string as_str;
       for (const auto& element : collection) {
          as_str += (element + " ");
       }
       return as_str;
    }