Search code examples
c++templatesc++17

How to implicitly convert `basic_string` to deduced `basic_string_view` template?


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);
      |     ~~~~~~~~~~~~~~^~~

Live Example

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.

  1. implicitly convert string to string_view
  2. basic_string to basic_string_view Implicit Conversion… ughhh why?

Solution

  • 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;
          /* ... */
      }
    };