我的目标是拥有一个接受特殊化 enable_if_t<>
别名的结构。连同类型名称可变参数包,然后告诉我是否 enable_if
包中的所有类型都满足了条件。我有一堆这些专门的 enable_if
s,但在我们将它们放入我们的开源项目之前需要为它们编写测试。我有大约 2000 多行代码手动测试这些特化,但我敢打赌,如果我能弄清楚下面的模式,我可以把它写到 100 或 200 行。我有一个工作版本(+ godbolt 链接),但我不确定它为什么工作,并且在实现接收参数包的情况下该方案被破坏
这是我想编写的代码示例及其结果。我正在使用 C++14 并且可以从 C++17 中窃取一些基本的实现,比如 conjunction 和 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
下面确实做了我想做的,但我不太明白怎么做。
// 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 {} ;
特别是,我不确定为什么 impl
中需要 void_t std::true_type
的部分特化.
我想得到的是 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
在第一个元函数中引起了我的误解
下面是 godbolt,非常感谢任何帮助理解这一点的人!
编辑:
为了提供更多背景信息,说明为什么我要在 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
的偏特化不匹配,它只有两个模板参数 <Check, void>
, 所以我们使用主模板
template <template <class> class Check, typename T, typename... Types>
struct require_tester;
与 Check = require_arithmetic
; T = double
; Types = double, int
.它与 require_tester
的偏特化不匹配.成员(member)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>
并且只给出了两个明确的模板参数,我们知道实际的模板参数是<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>>
是void_t<require_arithmetic<double>>
是void
.这确实匹配 void
来自模板参数,所以偏特化匹配,require_template_impl<require_arithmetic, double, void>
继承std::true_type
, 不是 std::false_type
.
另一方面,如果T1
是std::string
而不是 double
, 将推导的模板参数替换为 void_t<require_arithmetic<std::string>>
无效,通过最终的 enable_if<
... >::type
哪里没有成员(member)type
存在。当将推导的模板参数替换为其他模板参数失败时,这意味着部分特化会因为不匹配而被丢弃。所以require_template_impl<require_arithmetic, std::string, void>
使用主模板并继承 std::false_type
.
回到value
require_tester
的成员, 它递归地找到 require_tester<require_arithmetic, double, int>::value
通过require_tester<require_arithmetic, int>::value
通过require_tester<require_arithmetic>::value
与 require_tester<require_arithmetic, void>::value
相同.所有 value
成员是真实的,所以最后value
是真的。
虽然我会稍微简化一下:
void
在require_tester
中是不必要的递归,并导致奇怪的“事实”require_tester<Anything, void>::value
总是正确的。最好删除= void
默认来自主require_tester
模板,并制作基本案例template <template <class> class Check> require_tester<Check>
相反。你的
value
require_tester
中的表达式主模板始终恰好为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
并没有多大用处。 , 我们需要确保使用适当的 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...> {};
参见 the modified program on godbolt , 并取得了预期的结果。
关于c++ - 如何简化模板模板参数中的enable_if别名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58189867/