为什么C++不支持强类型省略号?

Why doesn't C++ support strongly typed ellipsis?

本文关键字:省略号 强类型 不支持 C++ 为什么      更新时间:2023-10-16

有人可以向我解释为什么C++,至少据我所知,没有实现强类型省略号函数,大意如下:

void foo(double ...) {
 // Do Something
}
意思是,简单

来说:"用户可以将可变数量的项传递给foo函数,但是,所有项都必须是双精度">

 void foo(std::initializer_list<double> values);
 // foo( {1.5, 3.14, 2.7} );

这非常接近于此。

您也可以使用可变参数模板,但它会变得更加分散。至于实际原因,我想说引入新语法的努力可能不值得:你如何访问单个元素?你怎么知道什么时候停止?是什么让它比std::initializer_list更好?

C++确实有更接近于此的东西:非类型参数包。

template < non-type ... values>

喜欢在

template <int ... Ints>
void foo()
{
     for (int i : {Ints...} )
         // do something with i
}

但是非类型模板参数(UHM(的类型有一些限制:例如,它不能double

从历史上看,省略号语法...来自 C。

这种复杂的野兽被用来为类似printf的功能提供动力,并与va_listva_start等一起使用。

正如你所指出的,它不是类型安全的;但是 C 远非类型安全的,它对任何指针类型进行从void*到的隐式转换,它对积分/浮点值的隐式截断等等......

因为C++要尽可能接近 C 的

超集,所以它继承了 C 的省略号。


自成立以来,C++实践不断发展,并且一直在大力推动更强的类型。

在C++11中,这最终导致:

  • 初始值设定项列表,给定类型的可变数量的值的简写语法:foo({1, 2, 3, 4, 5})
  • 可变参数模板,它们本身就是一头野兽,例如允许编写类型安全的printf

可变参数模板实际上在其语法中重用省略号...,以表示类型或值的,并作为解包运算符:

void print(std::ostream&) {}
template <typename T, typename... Args>
void print(std::ostream& out, T const& t, Args const&... args) {
    print(out << t, args...); // recursive, unless there are no args left
                              // (in that case, it calls the first overload
                              // instead of recursing.)
}

请注意...的 3 种不同用途:

  • typename...声明可变参数类型
  • Args const&...声明一组参数
  • args...在表达式中解压缩包装

使用可变参数模板和 SFINAE 已经可以实现:

template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;
template <class... Doubles, class = std::enable_if_t<
    all_true<std::is_convertible<Doubles, double>{}...>{}
>>
void foo(Doubles... args) {}

感谢哥伦布的好all_true把戏。您还可以在 C++17 中使用折叠表达式。

由于后来和即将推出的标准都专注于更简洁的语法(简洁的 for 循环、隐式函数模板......(,您提出的语法很可能有一天会进入标准;)

为什么没有提出(或被提出并拒绝(这样的事情,我不知道。这样的东西肯定会有用,但会增加语言的复杂性。正如昆汀所展示的那样,已经提出了一种C++11方法可以通过模板实现这种目标。

当概念画板被添加到标准中时,我们将有另一种更简洁的方式:

template <Convertible<double>... Args>
void foo(Args... doubles);

template <typename... Args>
    requires Convertible<Args, double>()...
void foo(Args... doubles);

或者,正如@dyp指出的那样:

void foo(Convertible<double>... doubles);    

就个人而言,在当前解决方案和我们将通过概念画板获得的解决方案之间,我认为这是解决问题的充分解决方案。特别是因为最后一个基本上是你最初要求的。

实现(某种程度上(您建议的方法是使用可变参数模板

template<typename... Arguments>
void foo(Arguments... parameters);

但是,您现在可以在参数包中传递任何类型。您提出的内容从未实现过,也许它可能是对语言的一个很好的补充,或者就目前的情况而言,它可能太难实现。您可以随时尝试撰写提案并将其提交给 isocpp.org

template<typename T, typename... Arguments>
struct are_same;
template <typename T, typename A1, typename... Args>
struct are_same<T, A1, Args...>{    static const bool value = std::is_same<T, A1>::value && are_same<T, Args...>::value;};
template <typename T>
struct are_same<T>{static const bool value = true;};
template<typename T, typename... Arguments>
using requires_same = std::enable_if_t<are_same<T, Arguments...>::value>;
template <typename... Arguments, typename = requires_same<double, Arguments...>>
void foo(Arguments ... parameters)
{
}

基于马修的回答:

void foo () {}
template <typename... Rest>
void foo (double arg, Rest... rest)
{
    /* do something with arg */
    foo(rest...);
}

如果使用foo的代码进行编译,则知道所有参数都可以转换为double

因为您可以使用

void foo(std::vector<T> values);