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.
#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);
...
}
}
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_if
s 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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With