【问题标题】:How to make a function accept arbitrary number of arguments not using f(...)?如何使函数接受不使用 f(...) 的任意数量的参数?
【发布时间】:2013-01-13 13:19:09
【问题描述】:

一段代码值一千字:

int main()
{
    // All of the following calls return true:
    AreEqual(1, 1);
    AreEqual(1, 1, 1);
    AreEqual(1, 1, 1, 1);
    AreEqual(1, 1, 1, 1, 1);

    // All of the following calls return false:
    AreEqual(1, 2);
    AreEqual(1, 2, 1);
    AreEqual(1, 7, 3, 1);
    AreEqual(1, 4, 1, 1, 1);    
}

如何实现接受任意数量参数的函数AreEqual()?

微不足道但乏味的灵魂是通过重载:

bool AreEqual(int v1, int v2);
bool AreEqual(int v1, int v2, int v3);
bool AreEqual(int v1, int v2, int v3, int v4);
......

另一个微不足道但不可行的解决方案是:

bool AreEqual(...);

此解决方案不可行,因为调用者必须添加另一个参数(参数计数或结束标记)来指定参数的数量。

另一种方法是通过可变参数模板参数

template<class... Args>
bool AreEqual(Args... args)
{
    // What should be placed here ???
}

【问题讨论】:

  • “任意”是指“任意类型的整数(例如前两个是ints,第三个是long)”还是“任意数量的参数同类型”?
  • 您可以将参数声明为long long,您将能够传递整数、短裤、长整数、字符、布尔值以及精度较低的任何内容。
  • 您究竟为什么要避免使用f(...)
  • @bames53,这个解决方案不可行,因为调用者必须传递一个参数来指定参数的数量。
  • @ValekHalfHeart 当然,但这既会强制转换(可能不是什么大问题),也可能会阻止该函数与自定义整数类型(例如大整数)一起使用。例如,@Rob 的解决方案适用于定义了 operator== 的所有类型。

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


【解决方案1】:

以下是如何使用模板来实现它:

#include <iostream>
#include <iomanip>

template<class T0>
bool AreEqual(T0 t0) { return true; }

template<class T0, class T1, class... Args>
bool AreEqual(T0 t0, T1 t1, Args ... args) {
  return t0 == t1 && AreEqual(t1, args...);
}


int main () {
  std::cout << std::boolalpha;

    // All of the following calls return true:
  std::cout<< AreEqual(1, 1) << "\n";
  std::cout<< AreEqual(1, 1, 1) << "\n";
  std::cout<< AreEqual(1, 1, 1, 1) << "\n";
  std::cout<< AreEqual(1, 1, 1, 1, 1) << "\n\n";

    // All of the following calls return false:
  std::cout<< AreEqual(1, 2) << "\n";
  std::cout<< AreEqual(1, 2, 1) << "\n";
  std::cout<< AreEqual(1, 7, 3, 1) << "\n";
  std::cout<< AreEqual(1, 4, 1, 1, 1)  << "\n";
}

您应该考虑这些变体是否适合您的使用:

  • 使用引用而不是按值传递
  • 使非可变模板采用两个参数而不是一个。


或者,这是一个非递归版本。可悲的是,它并没有短路。要查看非递归短路版本,请参阅其他答案。
template<typename T, typename... Args>
bool AreEqual(T first, Args... args)
{
  return std::min({first==args...});
}


最后,这个版本的吸引力在于它缺乏微妙之处:
template<typename T, typename... Args>
bool AreEqual(T first, Args... args)
{
  for(auto i : {args...})
    if(first != i)
      return false;
  return true;
}

【讨论】:

  • 他们应该是inline
  • @Utaal:为什么?模板已经存在某种 ODR“异常”。
  • @GManNickG 我指的是性能/代码大小,而不是重复的定义。如果我没记错的话,AreEqual(1, 4, 1, 1, 1) 被扩展为 5 个非模板“函数”(第一个接受 5 个参数,最后一个只接受一个)。这意味着,如果没有内联,您将支付 5 次函数调用的相当大的开销来计算 5 个整数的相等性。我还不能确认这一点,但感觉编译器应该能够内联所有这些调用。
  • 如果你的编译器好用,不管你是否使用inline关键字,它都会生成(或不)内联代码。
  • @Robᵩ, @GManNickG - 哦,我搜索了一下,才意识到我一直在使用inline (主要是)错误的原因。我知道inline 经常被忽略,但我不知道非inline 函数无论如何都会被内联。
【解决方案2】:

由于您似乎出于某种原因排除了明智的做法,您也可以尝试使用std::initializer_list

template<typename T>
bool AreEqual(std::initializer_list<T> list) {
    ...
}

那么你可以这样称呼它:

AreEqual({1,1,1,1,1});

【讨论】:

  • +1 启用漂亮的实现 return std::adjacent_find(list.begin(), list.end(), std::not_equal_to&lt;T&gt;()) == list.end();return std::all_of(std::next(list.begin()), list.end(), std::bind(std::equal_to&lt;T&gt;(), *list.begin(), std::placeholders::_1));(它对第一个元素的特殊处理不太漂亮,但正确使用 == 而不是 !=,虽然 @987654328 @ 无论如何都应该与 != 同步)。
【解决方案3】:

这是一个非递归版本:

template <typename T> using identity = T;

template<typename T, typename... Args>
bool AreEqual(T first, Args... args)
{
  bool tmp = true;
  identity<bool[]>{tmp?tmp=first==args:true ...};
  return tmp;
}

启用优化后,与递归函数相比没有任何开销,只是行为可能有所不同,因为所有参数都与第一个参数进行比较。

【讨论】:

    【解决方案4】:

    使用

     bool AreEqual(int v1, ...);
    

    但是,您需要以某种方式检测整数列表的结尾。如果有一个特定的整数值不能合法地传递给函数,那么就使用它。例如,如果所有整数都是正数,您可以使用-1 来标记结束。否则,您可以将第一个参数设为计数。

    这是计数版本:

    bool AreEqual(int count, int v1, ...)
    {
         va_list vl;
         va_start(vl, count);
         for(int i = 1; i < count; ++i)
            if (va_arg(v1, int) != v1)
            {
                va_end(vl);
                return false;
            }
         va_end(vl);
         return true;
    }
    

    这是结束标记版本:

    bool AreEqual(int v1, ...)
    {
         va_list vl;
         va_start(vl, count);
    
         do
         {
             int param = va_arg(vl, int);
             if (param == -1)
             {
                 va_end(vl);
                 return true;
            }
        } while (param == v1);
        va_end(vl);
        return false;
    }
    

    【讨论】:

    • 这个方案不可行,因为调用者必须传递一个参数来指定参数的个数。
    • @xmllmx:这是一种选择。另一种选择是在列表末尾使用标记。
    • 强制调用者附加标记会改变函数接口。
    • 我同意。但是由于您没有实现(否则,为什么要问这个问题?)没有代码依赖于接口。所以它很容易改变。
    • 我有一个实现,就是重载方案。但是我觉得不优雅,所以问了这个问题。
    【解决方案5】:

    可变参数模板需要对特化进行递归:

    template<class A> //just two: this is trivial
    bool are_equal(const A& a, const A& b)
    { return a==b; } 
    
    template<class A, class... Others>
    bool are_equal(const A& a, const A& b, const Others&... others)
    { return are_equal(a,b) && are_equal(b,others...); }
    

    本质上,每次 are_equal 嵌套时,其他...都会短一直到 estinguish,并且该函数将绑定两个 args。

    注意: 通过使用A 作为ab 的类型,并且因为Others 的第一个总是匹配一个A,这实际上使得are_equal(...) 只接受一个same 类型(至少,可转换为第一个参数的类型)。

    虽然我喜欢这个约束通常很有用,但可以通过使用 AB 作为 ab 的类型来放松限制 这使得该函数适用于每对存在operator== 的每组类型。

    template<class A, class B> //just two: this is trivial
    bool are_equal(const A& a, const B& b)
    { return a==b; } 
    
    template<class A, class B, class... Others>
    bool are_equal(const A& a, const B& b, const Others&... others)
    { return are_equal(a,b) && are_equal(b,others...); }
    

    【讨论】:

    • 我认为这个解决方案是最好的。简洁、优雅、美观!这个答案应该会得到更多的支持。
    猜你喜欢
    • 2018-11-04
    • 1970-01-01
    • 2011-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多