如何正确实现具有可变数量 std::string_view 个参数的函数?

How to properly implement a function with variadic number of std::string_view arguments?

期望的行为

我基本上想要的是创建一个这样的函数:

void func(std::string_view... args)
{
    (std::cout << ... << args);
}

它应该能够 与可转换为 std::string_view.

的 类 一起工作

示例:

int main()
{
    const char* tmp1 = "Hello ";
    const std::string tmp2 = "World";
    const std::string_view tmp3 = "!";

    func(tmp1, tmp2, tmp3, "\n");

    return 0;
}

应该打印:Hello World!


完成的行为

到目前为止,我到达这里:

template<typename... types>
using are_strings = std::conjunction<std::is_convertible<types, std::string_view>...>;

template<typename... strings, class = std::enable_if_t<are_strings<strings...>::value, void>>
void func(strings... args)
{
    (std::cout << ... << args);
}

int main()
{
    const char* tmp1 = "Hello ";
    const std::string tmp2 = "World";
    const std::string_view tmp3 = "!";

    func(tmp1, tmp2, tmp3, "\n");

    return 0;
}

这确实符合预期,但仍然存在一个大问题。


问题

只有可以转换为 std::string_view 的 类 才能在这个函数中使用,这很好。
但是,即使 类 可以转换,它们 也不会 转换为 std::string_view

这会导致不必要的数据复制(例如,当 std::string 作为参数传递时)。


问题

有没有办法强制将可变参数隐式转换为 std::string_view


备注

我知道 std::initializer_list,但我想保持函数调用简单,没有 {}

不完全是你问的...但是如果你可以为 args... 的长度设置一个上限(在下面的例子中是 9)我提出以下解决方案: foo<N> 结构继承 N func() 接受 0, 1, 2, ..., N std::string_view.

的静态函数

这样,func() 函数接受可转换为 std::string_view 的内容,所有参数都转换为 std::string_view.

正是

void func(std::string_view... args)
{ (std::cout << ... << args); }

不同之处在于 func() 函数是 foo<N> 中的 static 方法,args... 长度有限制并且有一个 func() 每个支持的长度的方法。

完整示例如下。

#include <string>
#include <utility>
#include <iostream>
#include <type_traits>

template <std::size_t ... Is>
constexpr auto getIndexSequence (std::index_sequence<Is...> is)
   -> decltype(is);

template <std::size_t N>
using IndSeqFrom = decltype(getIndexSequence(std::make_index_sequence<N>{}));

template <typename T, std::size_t>
struct getType
 { using type = T; };

template <typename, typename>
struct bar;

template <typename T, std::size_t ... Is>
struct bar<T, std::index_sequence<Is...>>
 {
   static void func (typename getType<T, Is>::type ... args)
    { (std::cout << ... << args); }
 };

template <std::size_t N, typename = std::string_view, 
          typename = IndSeqFrom<N>>
struct foo;

template <std::size_t N, typename T, std::size_t ... Is>
struct foo<N, T, std::index_sequence<Is...>> : public bar<T, IndSeqFrom<Is>>...
 { using bar<T, IndSeqFrom<Is>>::func ...; };


int main ()
 {
    const char* tmp1 = "Hello ";
    const std::string tmp2 = "World";
    const std::string_view tmp3 = "!";

    foo<10u>::func(tmp1, tmp2, tmp3, "\n");
 }
namespace impl{
  template<class...SVs>
  void func(SVs... svs){
    static_assert( (std::is_same< SVs, std::string_view >{} && ...) );
    // your code here
  }
}
template<class...Ts,
  std::enable_if_t< (std::is_convertible<Ts, std::string_view >{}&&...), bool > =true
>
void func( Ts&&...ts ){
  return impl::func( std::string_view{std::forward<Ts>(ts)}... );
}

或类似的东西。

#include <string_view>
#include <utility>

template <typename>
using string_view_t = std::string_view;

template <typename... Ts>
void func_impl(string_view_t<Ts>... args)
{
    (std::cout << ... << args);
}

template <typename... Ts>
auto func(Ts&&... ts)
    -> decltype(func_impl<Ts...>(std::forward<Ts>(ts)...))
{
    return func_impl<Ts...>(std::forward<Ts>(ts)...);
}

DEMO

分两步制作:

template <class... Args>
std::enable_if_t<... && std::is_same<Args, std::string_view>()>
func(Args... args)
{
    (std::cout << ... << args);
}

template <class... Args>
auto func(Args&&... args)
-> std::enable_if_t<... || !std::is_same<std::decay_t<Args>, std::string_view>(),
    decltype(func(std::string_view(std::forward<Args>(args))...))>
{
    func(std::string_view(std::forward<Args>(args))...);
}

如果您只是想避免不必要的数据复制,请使用前向引用,然后执行显式转换(如果仍然需要)。这样就没有数据被复制但被转发(在你的main.cpp示例中,所有参数都作为常量引用传递)

template <typename... strings,
          class = std::enable_if_t<are_strings<strings...>::value, void>>
void func(strings&&... args) {
  (std::cout << ... << std::string_view{args});
}