检查函数模板的所有参数包是否都属于整数类型。

huangapple go评论66阅读模式
英文:

Check all parameter pack args of function template belong to int

问题

I want my function to accept variadic args of type int only. For example, to compute sum (taken from C++ core guidelines):

template<class ...Args>
auto sum(Args... args) // GOOD, and much more flexible
{
    return (... + args); // note: C++17 "fold expression"
}

Taking this simple example from std::conjunction -

template<typename T, typename... Ts>
std::enable_if_t<std::conjunction_v<std::is_same<T, Ts>...>, int>
control(T, Ts...)
{
    std::cout << "all types in pack are T\n";
    return 0;
}

seems to check type T against Ts, which isn't something I want (it will compile successfully if all args of type say double).

How do I go about doing that?

Secondly, does the conjunction example split T and Ts just for the purpose of checking types, and it's intended usage is just one set of variadic args? What is this pattern called?

英文:

I want my function to accept variadic args of type int only. For example, to compute sum (taken from C++ core guidelines):

template&lt;class ...Args&gt;
auto sum(Args... args) // GOOD, and much more flexible
{
    return (... + args); // note: C++17 &quot;fold expression&quot;
}

Taking this simple example from std::conjunction -

template&lt;typename T, typename... Ts&gt;
std::enable_if_t&lt;std::conjunction_v&lt;std::is_same&lt;T, Ts&gt;...&gt;, int&gt;
control(T, Ts...)
{
    std::cout &lt;&lt; &quot;all types in pack are T\n&quot;;
    return 0;
}

seems to check type T against Ts, which isn't something I want (it will compile successfully if all args of type say double).

How do I go about doing that?

Secondly, does the conjunction example split T and Ts just for the purpose of checking types, and it's intended usage is just one set of variadic args? What is this pattern called?

答案1

得分: 1

你可以在 enable_if 中使用折叠表达式:

template<typename... Args, 
         std::enable_if_t<(... && std::is_same_v<Args, int>), bool> = true>
auto sum(Args... args) // 良好,更加灵活
{
    return (... + args); // 注意:C++17 中的 "fold expression"
}
英文:

You can use a fold expression in the enable_if:

template&lt;typename... Args, 
         std::enable_if_t&lt;(... &amp;&amp; std::is_same_v&lt;Args, int&gt;), bool&gt; = true&gt;
auto sum(Args... args) // GOOD, and much more flexible
{
	return (... + args); // note: C++17 &quot;fold expression&quot;
}

答案2

得分: 1

Here is the translated code segment you requested:

如果您想要一个仅接受可变数量的`int`的可变参数函数,可以使用`std::enable_if`中的折叠表达式来实现:

```cpp
template<class ...Args>
auto sum(Args... args)
    -> std::enable_if_t<(... && std::is_same_v<Args, int>), int>
// 注意:最好在返回类型中使用 std::enable_if_t。
// 实际返回类型是int,std::enable_if_t只是为了进行SFINAE。
{
    // 注意:使用零以获取空包的值
    return (0 + ... + args);
}

在这里使用std::conjunction是不必要的,特别是在C++17中,折叠表达式同样可以胜任。是的,将包分为TTs只是为了能够通过std::is_same_v<T, Ts>...将所有Ts与第一个类型进行比较。

然而,这个设计非常值得质疑,因为:

  • 这将实例化单独的函数sum<int>sum<int, int>,...(对于编译性能不利)
  • 我们不能使用longfloat类型的参数调用此函数,因为替换将失败,即使它们是常数,并且我们知道转换为int是安全的

一个更合理的设计是使用std::initializer_list<int>

#include <numeric>

int sum(std::initializer_list<int> args)
{
    return std::reduce(args.begin(), args.end());
}

// 使用 sum({1, 2, 3, 4}) 调用

是的,这将需要我们使用额外的大括号,但隐式转换可以正常工作,并且该函数甚至不再是一个模板,因此是值得的。


<details>
<summary>英文:</summary>

If you want a variadic function that only accepts a variable amount of `int`s, a fold expression in `std::enable_if` could do that:
```cpp
template&lt;class ...Args&gt;
auto sum(Args... args)
    -&gt; std::enable_if_t&lt;(... &amp;&amp; std::is_same_v&lt;Args, int&gt;), int&gt;
// note: It&#39;s best to use std::enable_if_t in return types.
//       The actual return type is int here, std::enable_if_t is just wrapping it
//       for the sake of doing SFINAE.
{
    // note: use zero to get a value for empty packs
    return (0 + ... + args);
}

Using std::conjunction is overkill here, especially since fold expressions get the job done just as well in C++17. And yes, splitting the pack into T and Ts was only done so that we can compare all Ts to the first type via std::is_same_v&lt;T, Ts&gt;....

However, this is really questionably design, because:

  • this will instantiate separate functions sum&lt;int&gt;, sum&lt;int, int&gt;, ... (bad for compilation performance)
  • we cannot call this function with arguments of type long or float, because substitution would fail, even if they are constants and we know that converting to int is safe

A much saner design would be to use std::initializer_list&lt;int&gt;:

#include &lt;numeric&gt;

int sum(std::initializer_list&lt;int&gt; args)
{
    return std::reduce(args.begin(), args.end());
}

// call with sum({1, 2, 3, 4})

Yes, this will require us to use extra braces, but implicit conversions work, and the function is not even a template anymore, making it worth it.

huangapple
  • 本文由 发表于 2023年6月29日 06:34:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/76577096.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定