Implementing a compile-time “static-if” logic for different string types in a container

戏子无情 提交于 2019-11-28 21:32:27
#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

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);
        ...
    }
}
Yakk - Adam Nevraumont

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>).

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)

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();
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!