【问题标题】:Using const parameter for variadic template对可变参数模板使用 const 参数
【发布时间】:2019-10-01 20:24:47
【问题描述】:

我有两个关于以下可变参数代码的问题:

#include <iostream>

template <typename T>
T Add(const T& arg_a)
{
    return arg_a;
}

template <typename T, typename... Pack>
T Add(const T& arg_a, const Pack&... arg_list)
{
    return arg_a + Add(arg_list...);
}

int main(int argc, const char* argv[])
{

    auto itLocalSum = Add(1, 2, 3, 4, 5);
    std::cout << "Sum of Add: " << itLocalSum << "\n";

    return 0;
}
  1. 为什么对参数使用 const 引用?
  2. 当我将浮点数与add(2.3, 43, 32.2) 之类的整数组合使用时,它不会向我显示正确的值。我该如何解决?

【问题讨论】:

  • 你从哪里得到这个代码?这不是泛型编程的一个很好的例子。

标签: c++ templates metaprogramming variadic-templates template-meta-programming


【解决方案1】:

问题 1 的答案

由于某些类型的复制成本很高,如果您只是简单地进行求和,不会修改底层对象,那么通过 const 引用传递就可以消除复制对象的成本。

例如,如果您通过值传递一个大的vector,则整个vector 将被复制以调用该函数,而传递一个 const 引用要快得多(可能已实现作为指针副本)。


问题 2 的答案

您的问题是,当您计算43 + 32.2 时,返回值为int,丢弃了小数部分。这是因为T在这个上下文中被推导出为文字43的类型,即int。您的问题的两种解决方案:

  1. 使用auto 作为返回值。 (如果您使用的是 c++ 14)
template <typename T>
T Add(const T& arg_a)
{
    return arg_a;
}

template <typename T, typename... Pack>
auto Add(const T& arg_a, const Pack&... arg_list)
{
    return arg_a + Add(arg_list...);
}
  1. 或者如果你有 c++ 17,只需使用fold expressions
template <typename... Pack>
auto Add(const Pack&... arg_list)
{
    return (... + arg_list);
}
  1. 如果您使用的是 c++ 11,请使用 std::common_type
template <typename T>
T Add(const T& arg_a)
{
    return arg_a;
}

template <typename T, typename... Pack>
typename std::common_type<T, Pack...>::type Add(const T& arg_a, const Pack&... arg_list)
{
    return arg_a + Add(arg_list...);
}

std::common_type不起作用时(例如Add('a', 'b', 'c', 'd')),您仍然可以在c++ 11中编写自己的sum类型推导器:

template <typename SumLeftT, typename ... Args>
struct sum_t_impl;

template <typename SumLeftT>
struct sum_t_impl<SumLeftT> {
    using type = SumLeftT;
};

template <typename SumLeftT, typename FirstT, typename ... Rest>
struct sum_t_impl<SumLeftT, FirstT, Rest...> {
    using type = typename sum_t_impl<decltype(std::declval<SumLeftT>() + std::declval<FirstT>()), Rest...>::type;
};

template <typename T, typename ... TArgs>
using sum_t = typename sum_t_impl<T, TArgs...>::type;

并将返回值类型typename std::common_type&lt;T, Pack...&gt;替换为sum_t&lt;T, Pack...&gt;

例如,这适用于带有chars 的包,其中char + char -&gt; int

int main()
{
    auto itLocalSum = Add('a', 'b', 'c', 'd');
    std::cout << typeid(itLocalSum).name() << std::endl;
    std::cout << "Sum of Add: " << itLocalSum << "\n";
    return 0;
}

将输出:int394

【讨论】:

  • @max66 谢谢,添加到答案中。
  • std::common_type 不是最好的选择,char+char -> int,而 common_type 是 char
  • @Jarod42 我添加了一个 sum 类型的推断器。
  • 为什么当我删除函数签名中的 const 关键字时,编译器给我错误“无法将参数 1 从 'int' 转换为 'T &'”。 @KaenbyouRin
  • @cayotee 你不能将左值引用绑定到prvalues
【解决方案2】:
  1. 为什么你通过引用传递参数?对于整数没关系,但我也可以Add(std::string{"QWER"}, std::string{"ASDF"}, std::string{"ZXCV"}) 并且复制它们会变得非常昂贵。

  2. 无论如何,您总是返回左侧类型。这意味着如果您有Add(2, 3.5),则结果必须是int,并且它将被强制转换为。

为避免它,您可以推断返回类型:

template <typename T, typename... Pack>
auto Add(const T& arg_a, const Pack&... arg_list)
{
    return arg_a + Add(arg_list...);
}

【讨论】:

    【解决方案3】:

    1) 只是接收参数,不做拷贝。 const 只是为了让它们“只读”,它们不能被改变。此外,当您使用 const 引用时,您可以将右值参数绑定到左值参数,然后您也可以将文字传递给您的函数,例如。

    2) 每当第一个参数是每对解包的整数时,都会隐式转换为 int。如果您将43 值更改为43.0,它将起作用。此外,在 C++17 中,您可以丢弃 Add 函数并使用折叠表达式:return (arg_a + ... + arg_list);

    可能更好的解决方案:

    template <typename... Pack>
    auto Add(const Pack&... arg_list) {
        return (arg_list + ...);
    }
    

    【讨论】:

      【解决方案4】:

      为什么参数使用 const 引用?

      当你打算调用它时:

      auto itLocalSum = Add(1, 2, 3, 4, 5);
      

      参数必须是 const&amp; 或只是值。即

      template <typename T>
      T Add(const T& arg_a)
      {
          return arg_a;
      }
      
      template <typename T, typename... Pack>
      T Add(const T& arg_a, const Pack&... arg_list)
      {
          return arg_a + Add(arg_list...);
      }
      

      template <typename T>
      T Add(T arg_a)
      {
          return arg_a;
      }
      
      template <typename T, typename... Pack>
      T Add(T arg_a, Pack... arg_list)
      {
          return arg_a + Add(arg_list...);
      }
      

      对于简单类型,两者都可以。如果T 的复制成本很高,const&amp; 的效率会更高。

      当我将浮点数与诸如 add(2.3, 43, 32.2) 之类的整数组合使用时,它不会向我显示正确的值。我该如何解决?

      您可以使用43.0 代替43

      当您使用Add(2.3, 43, 32.2) 时,递归调用Add(43, 32.2) 返回一个int 并将返回值截断为75

      【讨论】:

        【解决方案5】:

        关于错误的总和......问题是

        template <typename T, typename... Pack>
        T Add(const T& arg_a, const Pack&... arg_list)
        {
            return arg_a + Add(arg_list...);
        }
        

        返回与第一个参数相同类型的值 (T)。

        因此,如果T 是一个整数,则您会丢失以下参数中的浮点部分。

        万一

        Add(2.3, 43, 32.2)
        

        你会发现,在43之后,32.2变成32

        要解决这个问题...如果可以使用C++17,可以使用模板折叠,简单写

        template <typename ... Ts>
        auto Add (Ts const & ... as)
         { return (as + ...); }
        

        如果不能使用C++17但可以使用C++14,可以如下模拟模板折叠

        template <typename ... Ts>
        auto Add (Ts const & ... as)
         {
           using unused = int[];
        
           typename std::common_type<Ts...>::type ret{};
        
           (void)unused { 0, ((void)(ret += as), 0)... };
        
           return ret;
         }
        

        观察std::common_type&lt;Ts...&gt;::type的使用,获取返回类型的变量。

        如果你只能使用 C++11(所以没有 auto 没有尾随返回类型),你必须明确返回类型

        template <typename ... Ts>
        typename std::common_type<Ts...>::type Add (Ts const & ... as)
         { 
           // same body as in C++14
         }
        

        【讨论】:

          猜你喜欢
          • 2016-12-01
          • 1970-01-01
          • 2012-05-02
          • 2021-10-01
          • 2017-08-28
          • 1970-01-01
          • 1970-01-01
          • 2016-10-06
          • 1970-01-01
          相关资源
          最近更新 更多