[英]How to simplify enable_if alias in template template parameter
我的目标是有一个结构,它接受一个专用enable_if_t<>
的别名以及一个类型名可变参数包,然后告诉我包中的所有类型是否满足enable_if
的条件。 我有一堆这样的专用enable_if
,但需要先为它们编写测试,然后才能将它们放入我们的开源项目中。 我有大约 2000 多行代码手动测试这些专业化,但我敢打赌,如果我能弄清楚下面的模式,我可以把它提高到 100 或 200 行。 我有一个工作版本(+ Godbolt 链接),但是我不确定它为什么工作,并且该方案在实现接收参数包的情况下会中断
这是我想编写的代码示例及其结果。 我正在使用 C++14 并且可以从 C++17 中窃取事物的基本实现,例如连接和 void_t
#include <type_traits>
#include <string>
// enable_if for arithmetic types
template <typename T>
using require_arithmetic = typename std::enable_if_t<std::is_arithmetic<T>::value>;
const bool true_arithmetic = require_tester<require_arithmetic, double, int, float>::value;
// output: true
// If any of the types fail the enable_if the result is false
const bool false_arithmetic = require_tester<require_arithmetic, double, std::string, float>::value;
// output: false
下面确实做了我想要的,但tbf我并不真正理解如何。
// Base impl
template <template <class> class Check, typename T1, typename = void>
struct require_tester_impl : std::false_type {};
// I'm not totally sure why void_t needs to be here?
template <template <class> class Check, typename T1>
struct require_tester_impl<Check, T1, void_t<Check<T1>>> : std::true_type {};
// The recursive version (stolen conjuction from C++17)
template <template <class> class Check, typename T = void, typename... Types>
struct require_tester {
static const bool value = conjunction<require_tester_impl<Check, T>,
require_tester<Check, Types...>>::value;
};
// For the end
template <template <class> class Check>
struct require_tester<Check, void> : std::true_type {} ;
特别是,我不确定为什么在std::true_type
的impl
部分专业化中需要 void_t 。
我想要的是一个require_variadic_tester
,它接受一个可变参数模板化别名,比如enable_if<conjunction<check<T...>>::value>
,并给我真或假。 可悲的是,无论输入什么类型,以下内容都会返回 false
// impl
template <template <class...> class Check, typename... Types>
struct require_variadic_impl : std::false_type {};
// Adding void_t here causes the compiler to not understand the partial specialiation
template <template <class...> class Check, typename... Types>
struct require_variadic_impl<Check, Check<Types...>> : std::true_type {};
template <template <class...> class Check, typename... Types>
struct require_variadic_tester : require_variadic_impl<Check, Types...> {};
给定输入,我想要以下内容,但似乎无法动摇如何将那个连词隐藏低一级
// Enable if for checking if all types are arithmetic
template <typename... Types>
using require_all_arithmetic = std::enable_if_t<conjunction<std::is_arithmetic<Types>...>::value>;
require_variadic_tester<require_all_arithmetic, double, double, double>::value;
// is true
require_variadic_tester<require_all_arithmetic, double, std::string, double>::value;
// is false
我认为我未能理解第一个元void_t
中的 void_t 导致我的误解
下面是神螺栓,非常感谢您对理解这一点的任何帮助!
为了提供更多背景信息,说明为什么我想要上面的内容与enable_if_t
内部的连词。 我被困在 C++14 上,但我们正在向我们的开源数学库添加一个新功能,如果没有更多的泛型类型(以及对这些泛型类型的要求),我们最终会出现大量代码膨胀。 我们目前有这样的东西
template <int R, int C>
inline Eigen::Matrix<double, R, C> add(
const Eigen::Matrix<double, R, C>& m1, const Eigen::Matrix<double, R, C>& m2) {
return m1 + m2;
}
我想要更多通用模板并做这样的事情
template <typename Mat1, typename Mat2,
require_all_eigen<is_arithmetic, Mat1, Mat2>...>
inline auto add(Mat1&& m1, Mat2&& m2) {
return m1 + m2;
}
我已经设置了所有这些require_*_<container>
别名,但是所有这些要求的测试大约有 2000 多行,并且在未来将是一个必须处理的时髦混乱。
我们有一元和可变参数模板 enable_if 别名,此时上面的一元案例做了我想要的一个很好的测试
#include <gtest/gtest.h>
TEST(requires, arithmetic_test) {
EXPECT_FALSE((require_tester<require_arithmetic, std::string>::value));
EXPECT_TRUE((require_tester<require_arithmetic, double, int, float>::value));
}
我遇到的问题是测试可变参数模板 enable_if 别名,我希望能够在其中编写类似的东西
// Enable if for checking if all types are arithmetic
template <typename... Types>
using require_all_arithmetic = std::enable_if_t<conjunction<std::is_arithmetic<Types>...>::value>;
/// For the tests
TEST(requires, arithmetic_all_test) {
EXPECT_FALSE((require_variadic_tester<require_all_arithmetic, std::string,
Eigen::Matrix<float, -1, -1>>::value));
EXPECT_TRUE((require_variadic_tester<require_all_arithmetic,
double, int, float>::value));
}
如果我可以测试所有这些,我认为仅requires
我们库的部分就可以是一个不错的 header 迷你库,我称之为“14 中的坏假概念”(或简称 bfc14;-))
这是您的require_tester<require_arithmetic, double, double, int>
发生的情况:
这与require_tester
的部分特化不匹配,它只有两个模板 arguments <Check, void>
,所以我们使用主模板
template <template <class> class Check, typename T, typename... Types>
struct require_tester;
用Check = require_arithmetic
; T = double
; Types = double, int
。 它与require_tester
的部分特化不匹配。 会员value
是结果
conjunction<require_tester_impl<Check, T>, require_tester<Check, Types...>>::value
其中有趣的部分是require_tester_impl<Check, T> = require_tester_impl<require_arithmetic, double>
。 首先,由于require_tester_impl
的模板参数是
template <template <class> class Check, typename T1, typename = void>
并且只给出了两个显式模板参数,我们知道实际的模板 arguments 是<require_arithmetic, double, void>
。 现在我们需要看看这是否匹配require_template_impl
的部分特化,所以我们尝试匹配:
require_template_impl<require_arithmetic, double, void>
require_template_impl<Check, T1, void_t<Check<T1>>>
所以模板参数推导找到Check = require_arithmetic
和T1 = double
。 类型void_t<Check<T1>>
不会导致Check
或T1
的任何扣除。 但是推导的参数值必须代入,我们发现void_t<Check<T1>>
is void_t<require_arithmetic<double>>
is void
。 这确实与模板 arguments 中的void
匹配,因此部分特化确实匹配,并且require_template_impl<require_arithmetic, double, void>
继承std::true_type
,而不是std::false_type
。
另一方面,如果T1
是std::string
而不是double
,则通过最终的enable_if<
... >::type
替换推导的模板 arguments 会发现void_t<require_arithmetic<std::string>>
无效,其中不存在成员type
。 当将推导出的模板 arguments 替换为其他模板参数失败时,这意味着部分特化被丢弃为不匹配。 所以require_template_impl<require_arithmetic, std::string, void>
使用主模板并继承std::false_type
。
回到require_tester
的value
成员,它递归地找到require_tester<require_arithmetic, double, int>::value
via require_tester<require_arithmetic, int>::value
via require_tester<require_arithmetic>::value
与require_tester<require_arithmetic, void>::value
。 所有的value
成员都是 true,所以最终的value
是 true。
虽然我会简化一下:
在require_tester
递归中void
是不必要的,并导致奇怪的“事实” require_tester<Anything, void>::value
始终为真。 最好从主require_tester
模板中删除= void
默认值,并改为使用基本案例template <template <class> class Check> require_tester<Check>
。
你在require_tester
主模板中的value
表达式总是正好给出两个模板 arguments 来conjunction
,所以它并没有真正使用它的可变参数属性,你也可以写require_tester_impl<
... >::value && require_tester<
... >::value
。 由于require_tester
本身正在执行递归,因此它不需要将递归定义抽象为conjunction
。 相反, require_tester
可以简化为依靠conjunction
并避免本身进行任何递归:
template <template <class> class Check, typename... Types> struct require_tester: conjunction<require_tester_impl<Check, Types>...> {}; // No other specialization needed.
require_variadic_tester
模板可以遵循类似的模式,除了我将给虚拟模板参数,它只是typename = void
一个名称, typename Enable
。 而且它需要出现在模板参数包之前,因此实际上将其默认为void
并没有多大用处,我们需要确保在相应的 position 中使用适当的void
模板参数。
template <template <class...> class Check, typename Enable, typename... Types>
struct require_variadic_impl : std::false_type {};
template <template <class...> class Check, typename... Types>
struct require_variadic_impl<Check, void_t<Check<Types...>>, Types...> : std::true_type {};
template <template <class...> class Check, typename... Types>
struct require_variadic_tester : require_variadic_impl<Check, void, Types...> {};
在 Godbolt 上查看修改后的程序,并获得所需的结果。
template <template <class> class Check, typename T1, typename = void>
struct require_tester_impl : std::false_type {};
// I'm not totally sure why void_t needs to be here?
template <template <class> class Check, typename T1>
struct require_tester_impl<Check, T1, void_t<Check<T1>>> : std::true_type {};
在这里,您需要require_tester_impl
的第三个参数是void
类型,因为您将其写为默认值。 如果用户在专门化require_tester_impl
时没有指定其第三个参数,则它是void
。 所以编译器会搜索一个偏特化,其中第一个模板参数是一元 class 模板,第二个模板参数是一个类型,第三个是void
,否则将找不到偏特化,因为 any 的第三个参数部分专业化将失败。
这就是void_t
发挥作用的地方。 由于您想将Check
注入参数,但您需要void
,这时void_t
就派上用场了,因为用于专门化的每种类型都映射到void
,这是您真正需要的。 当部分特化没有失败时,您将拥有两个启用的特化,一个是默认的,一个是部分的。
最终会选择部分的,因为它比另一个更专业,因为void
的计算方式取决于其他模板参数。
这是第一部分。 对于第二部分(可变参数模板),请记住,如果enable_if
成功,它将返回void
。
所以你的require_variadic_impl
:
template <template <class...> class Check, typename... Types>
struct require_variadic_impl : std::false_type {};
// Adding void_t here causes the compiler to not understand the partial specialiation
template <template <class...> class Check, typename... Types>
struct require_variadic_impl<Check, Check<Types...>> : std::true_type {};
这里有一个问题,就是Check<Types...>
,因为它被别名为enable_if
,所以在成功时返回void
,但是, require_variadic_impl
的第二个参数不是void
所以当检查时部分特化最终失败正确的。 如果不是,则enable_if
没有定义内部类型,部分特化也失败,并且再次使用基本情况。
但是,做起来很简单。 我在这里提出一个更具可读性的实现,最终结果相同:
#include <iostream>
#include <type_traits>
#include <string>
template<class... Ts>
struct require_all_arithmetic : std::conjunction<std::is_arithmetic<Ts>...>
{};
template<template<class...> class Check, class... Ts>
struct require_variadic_tester : Check<Ts...>
{};
int main()
{
std::cout << require_variadic_tester<require_all_arithmetic, double, double, double>::value << std::endl;
std::cout << require_variadic_tester<require_all_arithmetic, double, std::string, double>::value << std::endl;
}
https://coliru.stacked-crooked.com/a/f9fb68e04eb0ad40
要不就:
#include <iostream>
#include <type_traits>
#include <string>
template<class... Ts>
struct require_all_arithmetic : std::conjunction<std::is_arithmetic<Ts>...>
{};
int main()
{
std::cout << require_all_arithmetic<double, double, double>::value << std::endl;
std::cout << require_all_arithmetic<double, std::string, double>::value << std::endl;
}
但是,如果您需要对 sfinae 友好的检查,以及将“sfinae”友好检查映射到true
/ false
的结构,则可以改用constexpr
方法。 它更简单:
template<class... Ts>
using require_all_arithmetic = std::enable_if_t<std::conjunction<std::is_arithmetic<Ts>...>::value>;
template<template<class...> class Check, class... Ts, class = Check<Ts...> >
constexpr bool require_variadic_tester_impl(int)
{ return true; }
template<template<class...> class Check, class... Ts>
constexpr bool require_variadic_tester_impl(unsigned)
{ return false; }
template<template<class...> class Check, class... Ts>
struct require_variadic_tester
{ static constexpr bool value = require_variadic_tester_impl<Check, Ts...>(42); };
int main()
{
std::cout << require_variadic_tester<require_all_arithmetic, double, double, double>::value << std::endl;
std::cout << require_variadic_tester<require_all_arithmetic, double, std::string, double>::value << std::endl;
}
该技术的工作原理如下:如果Check
失败,则仅编译第二个重载,它返回false
。 但是,如果检查有效并且定义了内部enable_if
,则两个重载都将有效,但是由于您传递了一个int
( 42
),并且第二个重载接收到一个unsigned
,第一个重载将是一个更好的匹配,返回true
。
https://coliru.stacked-crooked.com/a/bfe22ea099dd5749
最后,如果您希望检查始终是true_type
或false_type
,那么,您可以使用别名std::conditional
,而不是继承:
template<template<class...> class Check, class... Ts>
using require_variadic_tester =
std::conditional_t<require_variadic_tester_impl<Check, Ts...>(42),
std::true_type, std::false_type>;
不确定是否了解您的所有需求,但...
我想要的是一个
require_variadic_tester
,它接受一个可变参数模板化别名,比如enable_if<conjunction<check<T...>>::value>
,并给我真或假。 可悲的是,无论输入什么类型,以下内容都会返回 false
您确定要conjunction<check<T...>>
吗?
或者你想要conjunction<check<T>...>
吗?
我的意思是...检查必须接收类型的可变参数列表,或者您是否要检查别名,(如在您的示例中)接收单个类型和当且仅当检查时为真的连词对所有类型都满意吗?
在第二种情况下, std::void_t
非常方便验证是否满足所有检查。
我提出以下require_variadic_impl
和require_variadic_tester
template <template <typename> class, typename, typename = void>
struct require_variadic_impl
: public std::false_type
{ };
template <template <typename> class C, typename ... Ts>
struct require_variadic_impl<C, std::tuple<Ts...>, std::void_t<C<Ts>...>>
: public std::true_type
{ };
template <template <typename> class C, typename ... Ts>
struct require_variadic_tester
: public require_variadic_impl<C, std::tuple<Ts...>>
{ };
现在从
template <typename T>
using require_arithmetic = typename std::enable_if_t<std::is_arithmetic<T>::value>;
// ...
printf("\nGeneric Variadic: \n\n");
const char* string_generic_var_check =
require_variadic_tester<require_arithmetic, std::string>::value ? "true" : "false";
const char* double_generic_var_check =
require_variadic_tester<require_arithmetic, double, double, double>::value ? "true" : "false";
std::printf("\t String: %s\n", string_generic_var_check);
std::printf("\t Double: %s\n", double_generic_var_check);
你得到
Generic Variadic:
String: false
Double: true
认为我在第一个元 function 中未能理解
void_t
导致我的误解
尝试std::void_t<Ts...>
视为“如果所有Ts
都启用则启用”。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.