Implementing static-if logic at compile time for different types of lines in a container

I would like to write a function template that works with a string container, like std::vector .

I would like to support both CString and std::wstring using 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 you call size() or length() for wstring.

If we had a "static if" function 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 any method of programming patterns to achieve this with the currently available C ++ 11/14 tools?

PS
I know that you can write a couple of DoSomething() overloads with vector<CString> and vector<wstring> , but this is not a question question.
Moreover, I would like this function template to work for any container in which you can iterate using a range loop.

+7
c ++ c ++ 11 templates containers compile-time
source share
5 answers
 #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

+16
source share

You can overload functions 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); ... } } 
+5
source share

One common way to solve this problem is to extract the required interface into the feature class. Something like that:

 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 come up with and change the function itself to enable the selection of features in addition to choosing the type:

 template <class ContainerOfStrings, class Traits = StringTraits<typename ContainerOfString::value_type>> void DoSomething(const ContainerOfStrings& strings) 
+4
source share

Here is an example with strong syntax.

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

Many templates:

 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's what it looks like at the 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 unlimited chain static_else_if .

This does not stop you from executing an unlimited chain of static_else ( static_else in the example above is just an alias for static_else_if<true> ).

+4
source share

You can provide two overloads to get 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(); } 
+3
source share

All Articles