Home > Blockchain >  How to let a template function accept anything you can construct some basic_string_view out
How to let a template function accept anything you can construct some basic_string_view out

Time:05-05

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:

Here's an new approach based on Yakks answer which seems to work with c 20; i'm unsure about the quality though:

#include <string_view>
#include <type_traits>

template<class T> concept HasValueType = requires { std::remove_cvref_t<T>::value_type; };
template<class T> concept HasNoValueType = !HasValueType<T>;

template <typename S>
struct CharType {};
template <HasNoValueType S>
struct CharType<S> {
    using type = std::remove_extent_t<std::remove_pointer_t<std::remove_cvref_t<S>>>;
};
template<HasValueType S>
struct CharType<S> {
    using type = typename std::remove_cvref_t<S>::value_type;
};
template<typename S>
using CharType_t = typename CharType<S>::type;

template<typename ZLike, template<class...>class Z>
concept can_become = requires (const ZLike& s) {
    { Z<CharType_t<decltype(s)>, std::char_traits<CharType_t<decltype(s)>>>(s) };
};

void func(const can_become<std::basic_string_view> auto& s) {
    using ct = CharType_t<decltype(s)>;
    auto sv = std::basic_string_view<ct, std::char_traits<ct>>{s};
    // use sv
}

Demo

CodePudding user response:

basic_string_view has a range version of CTAD in C 23, so in C 23, you can use the requires clause to constrain basic_string_view{s} to be well-formed, and deduce its type by borrowing the CTAD of basic_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
}

Demo

CodePudding user response:

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));
}

CodePudding user response:

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 from const char[1] to std::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.

CodePudding user response:

@康桓瑋'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
}

Live example.

This can also be solved using and earlier techniques:

void func(const can_become<std::basic_string_view> auto& s) {
  auto sv = std::basic_string_view{s};
  // use sv
}
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 that std::basic_string_view had a CTAD that was SFINAE friendly. Without it, you have to know the exact kind of basic_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.

  • Related