如何使用可变数目的std :: string_view参数正确实现函数?

时间:2018-12-08 20:34:53

标签: c++ c++17 variadic-templates variadic-functions

期望的行为

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

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,但是我想保持函数调用简单,而无需使用{}

5 个答案:

答案 0 :(得分:5)

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

或类似的东西。

答案 1 :(得分:2)

#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

答案 2 :(得分:2)

如果只是想避免不必要的数据复制,请使用前向引用,然后执行显式强制转换(如果仍然需要)。这样就不会复制任何数据,而是将其转发(在您的main.cpp示例中,所有参数都作为const引用传递)

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

答案 3 :(得分:0)

并非完全是您的要求...但是如果您可以为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()函数是static内部的foo<N>个方法,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");
 }

答案 4 :(得分:0)

使其分为两个阶段:

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