How to let a template function accept anything you can construct some basic_string_view out
I'm trying to write a simple template function which accepts all possible basic_string_view but i always get the compiler error "no matching overloaded function found".
I don't know the reason; explicitly converting to string_view by the caller works but i'd like to avoid that; or is intentionally made hard?
Are there deduction guidelines which prevent this?
And is there a easy way to implement this as a template?
Here (and on godbolt) is what i tried:
#include <string_view>
#include <string>
template <typename CharT, typename Traits> void func(std::basic_string_view<CharT, Traits> value)
//template <typename CharT> void func(std::basic_string_view<CharT> value)
//void func(std::string_view value)
{}
int main() {
std::string s;
std::string_view sv(s);
char const cs[] = "";
std::string_view csv(cs);
std::wstring ws;
std::wstring_view wsv(ws);
wchar_t const wcs[] = L"";
std::wstring_view wcsv(wcs);
func(s);
func(sv);
func(cs);
func(csv);
func(ws);
func(wsv);
func(wcs);
func(wcsv);
}
Here are the errors msvc, clang and gcc show:
error C2672: 'func': no matching overloaded function foundx64 msvc v19.latest #3
error C2783: 'void func(T)': could not deduce template argument for '<unnamed-symbol>'x64 msvc v19.latest #3
error: no matching function for call to 'func'x86-64 clang (trunk) #1
error: no matching function for call to 'func(std::string&)'x86-64 gcc (trunk) #2
EDIT:
Demo of a blend of Yakks c++20 answer with the addition of Jonathans raw character pointer support.
4 answers
-
answered 2022-05-04 11:05
Quimby
It is in general impossible for the compiler to deduce the template arguments so a conversion can succeed. That is not how C++ templates were designed.
Given this:
char const cs[] = ""; func(cs);
The compiler would have to be able to answer
"What template arguments
CharT
,Traits
must I deduce so there is an appropriate implicit conversion fromconst char[1]
tostd::basic_string_view<CharT,Traits>
?"Of course it cannot do that, at least not without somehow iterating over all types (which is infinite, countable set).
Unfortunately there are no deduction guides for template functions.
-
answered 2022-05-04 11:05
康桓瑋
basic_string_view
has a range version of CTAD in C++23, so in C++23, you can use therequires
clause to constrainbasic_string_view{s}
to be well-formed, and deduce its type by borrowing the CTAD ofbasic_string_view
in the function body#include <string_view> template<typename StrLike> requires requires (const StrLike& s) { std::basic_string_view{s}; } void func(const StrLike& s) { auto sv = std::basic_string_view{s}; // use sv }
-
answered 2022-05-04 11:36
Jonathan S.
It's also possible to do this using just C++20 by checking whether the argument passed to the function is a range. The range can then be converted to a
basic_string_view
using its constructor overload that takes iterators.I've also added an overload that can deal with char pointers since those aren't ranges.
#include <string_view> #include <string> #include <type_traits> #include <ranges> template <typename CharT, typename Traits> void func(std::basic_string_view<CharT, Traits> value) { // ... } template<typename S> requires std::ranges::contiguous_range<S> void func(const S& s) { func(std::basic_string_view{std::ranges::begin(s), std::ranges::end(s)}); } template<typename S> requires (!std::ranges::range<S> && requires (S s) { std::basic_string_view<std::remove_cvref_t<decltype(s[0])>>(s); }) void func(const S& s) { func(std::basic_string_view<std::remove_cvref_t<decltype(s[0])>>(s)); }
-
answered 2022-05-04 16:10
Yakk - Adam Nevraumont
@康桓瑋's answer is awesome. But you should avoid unnamed concepts. So:
#include <string_view> template<typename ZLike, template<class...>class Z> concept can_become = requires (const ZLike& s) { { Z{s} }; }; void func(const can_become<std::basic_string_view> auto& s) { auto sv = std::basic_string_view{s}; // use sv }
This can also be solved using c++17 and earlier techniques:
template<class StrLike, std::enable_if_t<can_become_v<StrLike, std::basic_string_view>, bool> = true> void func2(const StrLike& s) { auto sv = std::basic_string_view{s}; // use sv }
without concepts.
It isn't until c++23 that
std::basic_string_view
had a CTAD that was SFINAE friendly. Without it, you have to know the exact kind ofbasic_string_view
your incoming type can convert to, and then use that specific type.operator basic_string_view<X,Y>()
can only be found if you have the specific type you want to cast to available.You can work around this by creating the range based constructors of
basic_string_view
, and manually deducing the type of the character (and possibly traits) involved.A pure c++20 solution that doesn't support raw char pointers:
template<typename ZLike, template<class...>class Z> concept can_range_become = requires (const ZLike& s) { { Z{std::ranges::begin(s), std::ranges::end(s)} }; }; template<typename T> concept stringlike = can_range_become<T, std::basic_string_view> && std::ranges::contiguous_range<T>; void func(const stringlike auto& s) { auto sv = std::basic_string_view{std::ranges::begin(s), std::ranges::end(s)}; // use sv }
do you know?
how many words do you know