3

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.

JeJo
  • 30,635
  • 6
  • 49
  • 88
Epic
  • 572
  • 2
  • 18
  • 4
    This sounds like your are looking for [template template arguments](https://en.cppreference.com/w/cpp/language/template_parameters). – Scheff's Cat Jul 14 '20 at 11:14
  • *"convert std containers to strings for logging purposes"* - this is a horrible idea, as the string operations (particularly `+=` when it needs to resize the buffer) will make slow dynamic memory allocations. You're better off streaming individual container elements and any separators/delimiters directly to the output stream. A convenient way to do that is to write a templated class that stores a reference to a container passed as a constructor argument, then have an `operator<<` to stream it. – Tony Delroy Jul 14 '20 at 11:24
  • 1
    @TonyDelroy: I would have outright agreed with you as recent as six months ago, but YMMV in my experience. Seems like it can be faster sometimes to build up a large string and then do "one" big write for it. – AndyG Jul 14 '20 at 11:26
  • @TonyDelroy, In addition to what AndyG said, using operator<< on a stream is not faster. see https://stackoverflow.com/questions/18892281/most-optimized-way-of-concatenation-in-strings – Epic Jul 14 '20 at 11:36
  • @Epic: I believe what Tony is saying is *slightly* different in that the stream he is talking about would not incur any extra overhead for each write. E.g., `std::cout`. The post you linked to uses a stringstream, which needs to append each string to an internal buffer. – AndyG Jul 14 '20 at 11:39

2 Answers2

4

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;
}
bartop
  • 9,971
  • 1
  • 23
  • 54
JeJo
  • 30,635
  • 6
  • 49
  • 88
2
#include <string>
#include <vector>

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<class T>
std::string my_to_string(T val) {
    return std::to_string(val);
}

template<>
std::string my_to_string(std::string val) {
    return val;
}

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

int main() {
    auto v = std::vector<int>{1, 2, 3};
    LOG_COLLECTION(v);

    auto v2 = std::vector<std::string>{"A", "aaa", "ee"};
    LOG_COLLECTION(v);
}
bartop
  • 9,971
  • 1
  • 23
  • 54