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 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.

  • 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 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

  • 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
    }
    

    Live example.

    This can also be solved using 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 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.

    A pure 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
    }
    

    Live example.

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum