Harry Asked:2020-12-18 02:50:35 +0800 CST2020-12-18 02:50:35 +0800 CST 2020-12-18 02:50:35 +0800 CST 具有可变数量参数的模板 - 但只有一个参数 772 受到这个问题的启发。 因此,有一些模板类,我们想向其传递未知数量的参数,但类型相同。 最简单的解决方案是通过参数化的initializer_list. 但是我们不是在寻找简单的方法 :) 并且我们希望使用具有可变数量参数的模板,但所有参数的类型都单一。并且有这个限制。 如何表达这样的限制?没关系 - 在 C++17 或 C++20 中(我在暗示概念)。 c++ 1 个回答 Voted Best Answer HolyBlackCat 2020-12-18T03:54:47+08:002020-12-18T03:54:47+08:00 有几种选择。 (1)如果参数的类型是预先知道的(可能是必须明确指定的模板参数),那么我们std::is_convertible也采用 SFINAE。 #include <iostream> #include <type_traits> template <typename template < typename T, typename ...P, typename = std::enable_if_t<(std::is_convertible_v<P, T> && ...)> > void foo(P &&... params) { auto print = [](auto &&x){std::cout << x << '\n';}; (print(T(std::forward<P>(params))) , ...); } int main() { foo<int>(1, 2.3, 3ll); // Печатает `1 2 3`. } 评论中建议了std::is_same,但不太方便。有了它,在传递参数时,有时需要显式转换类型:foo<int>(1, int(2.3), int(3ll)); 由于std::is_convertible参数最终可能是不同的(隐式可转换的T)类型,因此在使用它们之前将它们转换为可能是有意义的T,如上面的示例所示。 如果您不喜欢(或在您的情况下不需要)通用引用,当然,您可以使用 const 引用: void foo(const P &... params) 甚至传递值: void foo(P ... params) 在这些情况下forward,当然不需要。 如果您不喜欢 SFINAE,可以删除最后一个模板参数并使用类似 static_assert((std::is_convertible_v<P, int> && ...), "Invalid argument types."); (2)如果事先不知道参数的公共类型,并且您希望编译器为您解决,请使用std::common_type. #include <iostream> #include <type_traits> template < typename ...P, typename T = std::common_type_t<P...> > void foo(P &&... params) { auto print = [](auto &&x){std::cout << x << '\n';}; (print(T(std::forward<P>(params))) , ...); } int main() { foo(1, 2.3, 3ll); // `T` определяется как `double`, печатает `1 2.3 3`. } 注意,如果编译器无法确定合适的泛型类型,typename T = std::common_type_t<P...>SFINAE就会被触发就行了。 如果你突然想用static_assertSFINAE 代替,那么你需要删除typename T并使用类似的东西 static_assert(std::experimental::is_detected_v<std::common_type_t, P...>, "Invalid argument types."); 。在这种情况下,为方便起见,你可以在函数内部添加using T = std::common_type_t<P...>;. (别忘了#include <experimental/type_traits>。) (3)通用选项。最方便,但需要更多的模板代码。 您可以显式指定类型,也可以将定义留给编译器。 #include <iostream> template <typename T, typename ...P> using maybe_explicit_common_type_t = typename std::conditional_t<std::is_void_v<T>, std::common_type<P...>, std::enable_if<(std::is_convertible_v<P, T> && ...), T> >::type; // Используем старомодный `typename ... ::type` чтобы SFINAE вдруг // неожиданно не сработал в отброшенной ветке. template < typename RawT = void, typename ...P, typename T = maybe_explicit_common_type_t<RawT, P...> > void foo(P &&... params) { auto print = [](auto &&x){std::cout << x << '\n';}; (print(T(std::forward<P>(params))) , ...); } int main() { foo(1, 2.3, 3ll); // `T` определяется как `double`, печатает `1 2.3 3`. foo<int>(1, 2.3, 3ll); // Печатает `1 2 3`. } 在这里,您也可以使用static_asserton std::experimental::is_detected,如选项(2)。
有几种选择。
(1)如果参数的类型是预先知道的(可能是必须明确指定的模板参数),那么我们
std::is_convertible
也采用 SFINAE。评论中建议了
std::is_same
,但不太方便。有了它,在传递参数时,有时需要显式转换类型:foo<int>(1, int(2.3), int(3ll));
由于
std::is_convertible
参数最终可能是不同的(隐式可转换的T
)类型,因此在使用它们之前将它们转换为可能是有意义的T
,如上面的示例所示。如果您不喜欢(或在您的情况下不需要)通用引用,当然,您可以使用 const 引用:
void foo(const P &... params)
甚至传递值:
void foo(P ... params)
在这些情况下
forward
,当然不需要。如果您不喜欢 SFINAE,可以删除最后一个模板参数并使用类似
static_assert((std::is_convertible_v<P, int> && ...), "Invalid argument types.");
(2)如果事先不知道参数的公共类型,并且您希望编译器为您解决,请使用
std::common_type
.注意,如果编译器无法确定合适的泛型类型,
typename T = std::common_type_t<P...>
SFINAE就会被触发就行了。如果你突然想用
static_assert
SFINAE 代替,那么你需要删除typename T
并使用类似的东西static_assert(std::experimental::is_detected_v<std::common_type_t, P...>, "Invalid argument types.");
。在这种情况下,为方便起见,你可以在函数内部添加
using T = std::common_type_t<P...>;
. (别忘了#include <experimental/type_traits>
。)(3)通用选项。最方便,但需要更多的模板代码。
您可以显式指定类型,也可以将定义留给编译器。
在这里,您也可以使用
static_assert
onstd::experimental::is_detected
,如选项(2)。