I am writing a generic algorithm that can work with strings or string segments of any character type -- and so I have decided to work with std::basic_string_view
with deduced template arguments for CharT
and Traits
. However, I have quickly discovered that this doesn't allow for passing std::basic_string
objects in, since the conversion operators are unable to contribute to the template deduction -- and so I am looking for a viable workaround.
At its core, I have a functor object that accepts some kind of basic_string_view
with deduced template arguments for CharT
and Traits
:
struct my_algorithm {
template <typename CharT, typename Traits>
auto operator()(std::basic_string_view<CharT,Traits> sv) -> void;
};
I would like to be able to pass this function an instantiation of some std::basic_string
without having to explicitly specify the template parameters to the function:
auto s = std::basic_string<char>{"hello world"}; // some basic_string, doesn't have to be '<char>'
my_algorithm{}(s); // fails to compile
The error this generates due to being unable to deduce the template overload is:
<source>: In function 'void test()':
<source>:13:19: error: no match for call to '(my_algorithm) (std::__cxx11::basic_string<char>&)'
13 | my_algorithm{}(s);
| ~~~~~~~~~~~~~~^~~
<source>:7:8: note: candidate: 'template<class CharT, class Traits> void my_algorithm::operator()(std::basic_string_view<_CharT, _Traits>)'
7 | auto operator()(std::basic_string_view<CharT,Traits> sv) -> void{}
| ^~~~~~~~
<source>:7:8: note: template argument deduction/substitution failed:
<source>:13:19: note: 'std::__cxx11::basic_string<char>' is not derived from 'std::basic_string_view<_CharT, _Traits>'
13 | my_algorithm{}(s);
| ~~~~~~~~~~~~~~^~~
Is it possible to work around this in-practice? I'm ideally looking for behavior similar to how this would be if it were statically defined, which works.
I can think of a couple potential workarounds, but each with substantial drawbacks:
Produce a series of overloads for my_algorithm::operator()(...)
for each string type I want to support (basic_string
, const CharT*
, etc). This unfortunately does not scale well, since my codebase has more than one "string" type with conversions to string_view
. Also the real code involves 2 string arguments, so this increases overloads exponentially.
Always manually convert std::basic_string
to std::basic_string_view
first before calling accept
(manual effort on the caller's part):
my_algorithm{}(std::string_view{s});
Is there any way to achieve this conversion without an exhaustive overload, and without the user being required to explicitly create a view?
Note: I have found two other questions asking something that sounds similar, but neither of them actually refer to deduced template types.
The simplest thing is to make it a more general template
struct my_algorithm {
template <typename StringView>
auto operator()(StringView&& sv) -> void {
using Traits = typename std::remove_cvref_t<StringView>::traits_type;
using CharT = typename std::remove_cvref_t<StringView>::value_type;
/* ... */
}
};
If you have other overloads, you will want to SFINAE that, which will be easier in C++20 with concepts
template <typename T>
concept stringlike = requires {
typename std::remove_cvref_t<T>::traits_type;
typename std::remove_cvref_t<T>::value_type;
}
struct my_algorithm {
template <stringlike StringView>
auto operator()(StringView&& sv) -> void {
using Traits = typename std::remove_cvref_t<StringView>::traits_type;
using CharT = typename std::remove_cvref_t<StringView>::value_type;
/* ... */
}
};