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

后端 未结 5 1315
南笙
南笙 2020-12-14 21: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

相关标签:
5条回答
  • 2020-12-14 22:02

    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);
            ...
        }
    }
    
    0 讨论(0)
  • 2020-12-14 22:08

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

    0 讨论(0)
  • 2020-12-14 22:09

    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();
    }
    
    0 讨论(0)
  • 2020-12-14 22:12
    #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

    0 讨论(0)
  • 2020-12-14 22:13

    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)
    
    0 讨论(0)
提交回复
热议问题