14

I'd like to write a function template that operates on a container of strings, for example a std::vector.

I'd like to support both CString and std::wstring with the same template function.

The problem is that CString and wstring have different interfaces, for example to get the "length" of a CString, you call the GetLength() method, instead for wstring you call size() or length().

If we had a "static if" feature in C++, I could write something like:

template <typename ContainerOfStrings>
void DoSomething(const ContainerOfStrings& strings)
{
    for (const auto & s : strings)
    {
        static_if(strings::value_type is CString) 
        {
            // Use the CString interface
        }
        static_else_if(strings::value_type is wstring)
        {   
            // Use the wstring interface
        }
    }
}

Is there some template programming technique to achieve this goal with currently available C++11/14 tools?

PS
I know it's possible to write a couple of DoSomething() overloads with vector<CString> and vector<wstring>, but that's not the point of the question.
Moreover, I'd like this function template to work for any container on which you can iterate using a range-for loop.

Mr.C64
  • 41,637
  • 14
  • 86
  • 162

5 Answers5

19
#include <type_traits>

template <typename T, typename F>
auto static_if(std::true_type, T t, F f) { return t; }

template <typename T, typename F>
auto static_if(std::false_type, T t, F f) { return f; }

template <bool B, typename T, typename F>
auto static_if(T t, F f) { return static_if(std::integral_constant<bool, B>{}, t, f); }

template <bool B, typename T>
auto static_if(T t) { return static_if(std::integral_constant<bool, B>{}, t, [](auto&&...){}); }

Test:

template <typename ContainerOfStrings>
void DoSomething(const ContainerOfStrings& strings)
{
    for (const auto & s : strings)
    {
        static_if<std::is_same<typename ContainerOfStrings::value_type, CString>{}>
        ([&](auto& ss)
        {
            // Use the CString interface
            ss.GetLength();
        })(s);

        static_if<std::is_same<typename ContainerOfStrings::value_type, wstring>{}>
        ([&](auto& ss)
        {
            // Use the wstring interface
            ss.size();
        })(s);
    }
}

DEMO

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Since I can mark only one answer, I'm choosing this because it resembles the structure of a "static if". However, when possible, I prefer simpler solutions like traits or function overloads. Thanks for sharing your code. – Mr.C64 Jun 10 '16 at 19:03
  • 1
    It seems like the lambda still has to compile and be passed to the appropriate static_if function, so if this compiles it is probably because the compiler optimizes away the unused parameter in the top two definitions. It should still be ill-formed, however, unless I'm missing something... – patatahooligan Jul 14 '17 at 12:53
  • @piotr-skotnicki why this does not work with gcc6 and gcc@7 on mac os? – sorosh_sabz Apr 02 '19 at 23:57
  • problem is for bug on gcc that reported on https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095 – sorosh_sabz Apr 03 '19 at 14:04
6

You could provide function overloads that do what you need:

size_t getSize(const std::string& str)
{
    return str.size();
}

size_t getSize(const CString& str)
{
    return str.GetLength();
}

template <typename ContainerOfStrings>
void DoSomething(const ContainerOfStrings& strings)
{
    for (const auto & s : strings)
    {
        ...
        auto size = getSize(s);
        ...
    }
}
Smeeheey
  • 9,906
  • 23
  • 39
4

One common way to solve this is to extract the required interface out into a trait class. Something like this:

template <class S>
struct StringTraits
{
  static size_t size(const S &s) { return s.size(); }
  // More functions here
};


template <typename ContainerOfStrings>
void DoSomething(const ContainerOfStrings& strings)
{
    for (const auto & s : strings)
    {
      auto len = StringTraits<typename std::decay<decltype(s)>::type>::size(s);
    }
}


// Anyone can add their own specialisation of the traits, such as:

template <>
struct StringTraits<CString>
{
  static size_t size(const CString &s) { return s.GetLength(); }
  // More functions here
};

Of course, you can then go fancy and change the function itself to allow trait selection in addition to the type-based selection:

template <class ContainerOfStrings, class Traits = StringTraits<typename ContainerOfString::value_type>>
void DoSomething(const ContainerOfStrings& strings)
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • I believe `StringTraits` won't work correctly inside the range for loop, considering the `const auto& s` iteration (`for (const auto & s : strings) ...`). Using the container's `value_type` instead is a better alternative. – Mr.C64 Jun 10 '16 at 12:48
  • @Mr.C64 `value_type` need not exist. I've fixed the `decltype` use. – Angew is no longer proud of SO Jun 10 '16 at 13:00
4

Here is one with a pretty syntax.

The goal is to get rid of the extra ()s in @Piotr's solution.

Lots of boilerplate:

template<bool b>
struct static_if_t {};
template<bool b>
struct static_else_if_t {};

struct static_unsolved_t {};

template<class Op>
struct static_solved_t {
  Op value;
  template<class...Ts>
  constexpr
  decltype(auto) operator()(Ts&&...ts) {
    return value(std::forward<Ts>(ts)...);
  }
  template<class Rhs>
  constexpr
  static_solved_t operator->*(Rhs&&)&&{
    return std::move(*this);
  }
};
template<class F>
constexpr
static_solved_t<std::decay_t<F>> static_solved(F&& f) {
  return {std::forward<F>(f)};
}

template<class F>
constexpr
auto operator->*(static_if_t<true>, F&& f) {
  return static_solved(std::forward<F>(f));
}
template<class F>
constexpr
static_unsolved_t operator->*(static_if_t<false>, F&&) {
  return {};
}
constexpr
static_if_t<true> operator->*(static_unsolved_t, static_else_if_t<true>) {
  return {};
}
constexpr
static_unsolved_t operator->*(static_unsolved_t, static_else_if_t<false>) {
  return {};
}

template<bool b>
constexpr static_if_t<b> static_if{};

template<bool b>
constexpr static_else_if_t<b> static_else_if{};

constexpr static_else_if_t<true> static_else{};

Here is what it looks like at point of use:

template <typename ContainerOfStrings>
void DoSomething(const ContainerOfStrings& strings) {
  for (const auto & s : strings)
  {
    auto op = 
    static_if<std::is_same<typename ContainerOfStrings::value_type,CString>{}>->*
    [&](auto&& s){
        // Use the CString interface
    }
    ->*static_else_if<std::is_same<typename ContainerOfStrings::value_type, std::cstring>{}>->*
    [&](auto&& s){   
        // Use the wstring interface
    };
    op(s); // fails to compile if both of the above tests fail
  }
}

with an unlimited chain of static_else_ifs supported.

It does not prevent you from doing an unlimited chain of static_else (static_else in the above is just an alias for static_else_if<true>).

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
3

You could provide two overloads for getting the length:

template<typename T>
std::size_t getLength(T const &str)
{
    return str.size();
}

std::size_t getLength(CString const &str)
{
  return str.GetLength();
}
101010
  • 41,839
  • 11
  • 94
  • 168