【问题标题】:Parameter pack expansion within parentheses gives bizarre output括号内的参数包扩展给出了奇怪的输出
【发布时间】:2016-07-05 06:00:40
【问题描述】:

我正在尝试实现一个函数,该函数接受可变数量的字符串并转发到打印函数,该函数期望每个字符串都有一个 char 指针和 size,交错。

例子:

std::string a = "123";
std::string b = "1234";
forward(a, b); // should call doPrint(a.c_str(), a.size(), b.c_str(), b.size())

我认为以下应该是正确的实现,但即使它编译了行为,我也很惊讶。

template <class ...Args>
void forward(const Args & ... args) {
  doPrint( (args.c_str(), args.size())...);
}

forward(a, b) 调用doPrint(3, 4),而不是doPrint("123", 3, "1234", 4),就好像我写了doPrint((args.size())...)。编译器完全忽略了对c_str() 的调用。

我尝试了g++clangicc,它们都产生了相同的输出。 (args.c_str(), args.size())... 有什么问题?

确实,std::make_tuple(args.c_str(), args.size())... 按预期工作,但假设我无法更改 doPrint 以接受和处理元组。

【问题讨论】:

    标签: c++ c++11 variadic-templates


    【解决方案1】:

    逗号运算符是一个表达式,其值为最后一个表达式的值。
    例如:

        int a = (1, 2, 3, 4, 5, 6);
        assert(a == 6);
    

    您可以尝试使用元组:

        doPrint(std::tuple_cat(std::make_tuple(argc.c_str(), args.size())...));
    

    然后doPrint 将需要更改为使用元组;如果需要,它可以将元组解包回参数包中,或者直接使用元组。

    解包元组示例:

        template <class Tuple, std::size_t ... indices>
        doPrint(Tuple t, std::integer_sequence<size_t, indices...>)
        {
            doPrint(std::get<indices>(t)...);
        }
    
        template <class Tuple>
        doPrint(Tuple t)
        {
            doPrint(t, std::make_index_sequence<std::tuple_size<Tuple>::value>());
        }
    

    不明确的函数名称可能会出现一些问题,因此您可能需要更改这些辅助函数的名称,但希望这足以让您开始。

    【讨论】:

    • 您的解决方案效果很好,谢谢!通过使用这个包装器,我根本不需要修改 doPrint,只需添加新的重载。我认为这三个点应该是左边的一个括号,但是:doPrint(std::tuple_cat( ...)) 而不是 doPrint(std::tuple_cat( )...),否则不会生成顶级元组,并且 tuple_cat 或多或少是一个 NOP。
    • @GeorgiosBitzes 你可以自己写make_index_sequenceinteger_sequence
    • int a = 1, 2, 3, 4, 5, 6; 不是有效的 C++。
    • @T.C.啊,你抓到我了!在这种情况下,括号有效。
    【解决方案2】:

    (args.c_str(), args.size()) 是一个逗号分隔的表达式,这意味着只有最后一部分 (args.size()) 会被传递给函数。

    然后它将对每个参数重复此操作,因此它实际上会调用 doPrint 并仅使用字符串大小!

    您应该更改doPrint 以使用元组,否则您必须使用一些疯狂的模板元编程东西。

    【讨论】:

      【解决方案3】:

      我可能会这样做以避免将元组暴露给编程接口:

      #include <string>
      #include <utility>
      #include <tuple>
      
      extern void doPrint(...);
      
      namespace detail {
        template<std::size_t...Is, class Tuple>
        void forward(std::index_sequence<Is...>, Tuple&& tuple)
        {
          doPrint(std::get<Is>(tuple)...);
        }
      }
      
      template<class...Strings>
      void forward(Strings&&... strings)
      {
        detail::forward(std::make_index_sequence<sizeof...(Strings) * 2>(),
                std::tuple_cat(std::make_tuple(strings.data(), strings.size())...)
                );
      }
      
      int main()
      {
        std::string a = "123";
        std::string b = "1234";
        forward(a, b); // should call doPrint(a.c_str(), a.size(), b.c_str(), b.size())
      }
      

      【讨论】:

        【解决方案4】:

        Jason Turner 在此视频中演示了一种使用初始化列表扩展可变参数模板的简洁方法:

        http://articles.emptycrate.com/2016/05/09/variadic_expansion_wrap_up.html

        template< typename ... T >
        void do_print(T ... args)
        {
            (void)std::initializer_list<int> {
                (std::cout << args.c_str() << ": "
                << args.size() << "\n",     0)...
            };
        }
        
        template< typename ... T >
        void forward_print(T ... args)
        {
            do_print(args...);
        }
        int main(int argc, const char * argv[])
        {
            std::cout << "Hello, World!\n";
        
            std::string a = "1234";
            std::string b = "567";
        
            forward_print(a, b);
        
            return 0;
        }
        

        这适用于 g++ -std=c++11

        【讨论】:

        • 有趣的技术,但是当您尝试将参数传递给另一个函数时,我看不出它是如何工作的。
        • 这个想法是你不需要使用这种技术重定向/专业化。我已经重写它以使用转发功能,但您可以直接调用 do_print。
        • 如果我没记错的话,do_print 在这种情况下会收到一个字符串参数包,这是不可取的。 doPrint 预计会以char*size 交错调用,它是我无法更改的外部库中的函数。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-06-25
        • 2015-09-27
        • 1970-01-01
        • 1970-01-01
        • 2023-04-01
        • 2020-12-29
        • 1970-01-01
        相关资源
        最近更新 更多