【问题标题】:Implicit conversions with variadic templates使用可变参数模板进行隐式转换
【发布时间】:2016-12-09 20:58:54
【问题描述】:

考虑两个函数调用

foo({"a", 1}, {"b", "value"});
foo({"a", 1}, {"b", "value"}, {"c", 1.0});

有没有办法为任意数量的参数对编写函数foo?

我在想一些事情

template <typename... Args>
void foo(std::pair<const char*, Args>&&...);

不幸的是,这不起作用。

gcc 失败并出现错误:

error: too many arguments to function 'void foo(std::pair<const char*, Args>&& ...) [with Args = {}]'
 foo({"aa", 1});

【问题讨论】:

    标签: c++ templates c++14 variadic-templates template-argument-deduction


    【解决方案1】:

    尝试简化您的示例并考虑以下问题:

    #include<utility>
    
    template<typename T>
    void foo(std::pair<char*, T>) {}
    
    int main() {
        foo({"a", 1});
    }
    

    它无法编译,as you can see。
    问题是{ "a", 1 } 不是std::pair,即使你可以从它构造一个,如下所示:

    #include<utility>
    
    void foo(std::pair<char*, int>) {}
    
    int main() {
        foo({"a", 1});
    }
    

    错误很明显:

    无法推断模板参数“T”

    你为什么不能?

    一旦知道T,编译器就可以构造这样的一对。无论如何,T 必须推导出来,而编译器不能这样做,因为 { "a", 1 } 不是可以推导出它的对。
    无论如何,{ "a", 1 } 可以转换为一对,在特定情况下转换为std::pair&lt;char *, T&gt; 的特化,但首先必须推导出T。
    从什么推导出来的?当然是一对,但你还没有一对。
    以此类推,循环往复。

    现在让我们讨论一下您尝试做一些涉及可变参数模板的类似操作:不用说,即使上面显示的更简单的示例也无法编译,它的可变参数扩展(如果有)也不会编译更多或更少的原因。

    有没有办法为任意数量的参数对编写函数 foo?

    我会说不,除非您使用对作为 foo 的参数。
    它遵循一个最小的工作示例:

    #include<utility>
    
    template <typename... Args>
    void foo(std::pair<const char*, Args>&&...) {}
    
    int main() {
        foo(std::make_pair("a", 1), std::make_pair("b", "value"));
    }
    

    如果你愿意,你也可以推导出第一个参数,只要它的类型是固定的:

    #include<utility>
    
    template <typename T, typename... Args>
    void foo(std::pair<T, Args>&&...) {}
    
    int main() {
        foo(std::make_pair("a", 1), std::make_pair("b", "value"));
    }
    

    否则如果不固定可以这样做:

    #include<utility>
    
    template <typename... First, typename... Second>
    void foo(std::pair<First, Second>&&...) {}
    
    int main() {
        foo(std::make_pair("a", 1), std::make_pair(0, "value"));
    }
    

    【讨论】:

      【解决方案2】:

      有没有办法为任意数量的参数编写函数 foo 对吗?

      有一些基于可变参数模板的解决方案,但参数必须成对以允许编译器推断类型。那么这样的事情可能会起作用:

      template<typename... Args>
      void foo() {}
      
      template<typename T, typename U, typename... Args>
      void foo(const std::pair<T, U>& p, Args... args) {
          std::cout << __PRETTY_FUNCTION__ << std::endl;
          foo(args...);
      }
      

      所以对于:

      foo(std::make_pair("a", 1), std::make_pair("b", "value"), std::make_pair("c", 1.0));
      

      输出(使用 clang 3.8)是:

      void foo(const std::pair<T, U> &, Args...) [T = const char *, U = int, Args = <std::__1::pair<const char *, const char *>, std::__1::pair<const char *, double>>]
      void foo(const std::pair<T, U> &, Args...) [T = const char *, U = const char *, Args = <std::__1::pair<const char *, double>>]
      void foo(const std::pair<T, U> &, Args...) [T = const char *, U = double, Args = <>]
      

      Here 是完整的工作示例。

      【讨论】:

      • 我已经用 clang 3.8 测试了你的代码,它不能编译。无论您的代码不符合标准。
      • @Oliv 我添加了答案的链接。现在您可以检查完整的代码。哪部分代码不符合标准?
      • @EdgarRokyan __PRETTY_FUNCTION__?
      • @skypjack 哈哈,是的,这是一个扩展,当然)
      • @EdgarRokyan 此外,您不必每次都从包装中取出一对。请参阅this answer 的最后一个 sn-p。它在某种程度上类似于 OP 在问题中发布的内容。我认为他对Args 感兴趣。
      【解决方案3】:

      要对Edgar Rokyan's answer 进行一些扩展,您可以将创建对移动到foo 函数中:

      template<typename... Args>
      void foo() {}
      
      // Forward declaration
      template<typename U, typename... Args>
      void foo(const char * str, U u, Args... args);
      
      // When given a pair
      template<typename U, typename... Args>
      void foo(const std::pair<const char *, U>& p, Args... args) {
          std::cout << p.first << " = " << p.second << std::endl;
          foo(args...);
      }
      
      // when given a C string and something else, make a pair
      template<typename U, typename... Args>
      void foo(const char * str, U u, Args... args) {
          foo(std::make_pair(str, u), args...);
      }
      

      那么你可以这样称呼它:

      foo("hi", 42,
          "yo", true,
          std::make_pair("Eh", 3.14),
          "foo", false,
          some_pair);
      

      【讨论】:

      • 请删除foo的第一个重载声明上方的template&lt;typename...Args&gt;(删除第一行)。你不调用任何 foo(),你只调用 foo()。将第一个空的 foo 重载声明为模板只会带来混淆:看起来模板特化和重载之间存在混淆。在您的代码中,您声明了三个重载 foo,但只使用了第一个重载的一个特化:foo(),因此第一个重载不需要是模板。
      • 更正:“只使用了第一个重载的一个实例化:foo()”
      【解决方案4】:

      在 c++17 中,您可以通过使用构造函数的模板推导来解决该问题并欺骗编译器:

      #include <iostream>
      #include <utility>
      
      template <class... Args>
      struct P:std::pair<Args...> {
          P(Args... args):std::pair<Args...>(args...) { }
      };
      
      template <class... Args>
      void foo(std::pair<const char *, Args>&&...) {
      }
      
      int main() {
          foo(P{"abc", 1}, P{"abc", "abc"}, P{"abc", 2.0});
      }
      

      [live demo]

      【讨论】:

      • 如果你可以使用构造函数的推导 - 你可以写foo(std::pair{"a", 1}/*...*/)。如果您不想写像std::pair 这样的“长”名称 - 写template&lt;typename T1, typename T2&gt; using P = std::pair&lt;T1, T2&gt;。不需要这个struct P 的东西。
      • @Rostislav 很好,也许在 c++17 中它现在可以工作 gcc 不允许它:example
      • 这很有趣,为什么这个额外的层适用于字符串文字,但直接pair 构造却不行(它似乎适用于非字符串文字)。
      • @Rostislav 我不会责怪 gcc,因为它还不完全支持 c++17 :)
      • @Rostislav 类模板参数推导不适用于别名模板。
      猜你喜欢
      • 1970-01-01
      • 2020-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多