我的目标是拥有一个结构,该结构采用专用enable_if_t<>
的别名以及类型名可变参数包,然后告诉我enable_if
的条件是否满足所有在包装中输入。我有一堆这些专门的enable_if
,但是需要为它们编写测试,然后才能将它们放入开源项目中。我大约有2000多行代码手动测试这些专业化,但是如果可以弄清楚下面的模式,我敢打赌我可以将其提高到100或200。我有一个可以正常工作的版本(+ godbolt链接),但是我不确定为什么它可以正常工作,并且在实现接收到参数包的情况下该方案也无法解决
这是我想编写的代码示例,它是结果。我正在使用C ++ 14,可以从C ++ 17中窃取事物的基本实现,例如joint和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 {} ;
尤其是,我不确定impl
的{{1}}部分专业化中为什么需要void_t。
我想要得到的是一个std::true_type
,它带有一个可变参数的模板化别名(类似于require_variadic_tester
),并给我带来了真假。不幸的是,无论输入什么类型,下面的内容都会返回false
enable_if<conjunction<check<T...>>::value>
我希望输入以下内容,但似乎无法动摇如何将该隐藏关系降低一级
// 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
内加连词的原因中提供更多背景信息。我坚持使用C ++ 14,但是我们要在开源数学库中添加一个新功能,如果没有更多的泛型类型(以及对这些泛型类型的要求),我们最终将花费大量的代码。我们目前有这样的东西
enable_if_t
我想拥有更多通用模板并执行类似的操作
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;
}
别名,但是对所有这些别名的测试大约需要2000多个行,并且将来将不得不处理一些时髦的事情。
我们有一元和可变的模板enable_if别名,这时上面的一元案例做了我想要的一个很好的测试
require_*_<container>
我遇到的问题是测试可变参数模板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 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));
}
部分可能只是一个很好的标头,只是我称之为“ 14中的不良假概念”(或简称bfc14)的微型库;-))
答案 0 :(得分:1)
您的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
的部分专业化不匹配。成员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
,则通过最终的{{1 }} ... void_t<require_arithmetic<std::string>>
,其中不存在成员enable_if<
。当将推导的模板参数替换为其他模板参数失败时,这意味着将部分专业化作为不匹配项丢弃。因此,>::type
使用主要模板并继承type
。
返回require_template_impl<require_arithmetic, std::string, void>
的{{1}}成员,它通过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
模板参数。 / p>
typename Enable
请参见the modified program on godbolt,并获得所需的结果。
答案 1 :(得分:0)
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
。因此,编译器将搜索部分专业化,其中第一个模板参数是一元类模板,第二个模板参数是类型,第三个模板参数是void
,否则将找不到部分专业化,因为任何部分专业化的第三个参数都会失败。
这就是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>;
答案 2 :(得分:0)
不确定要了解您的所有需求,但是...
我想要得到的是一个
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
认为我在第一个元函数中无法理解
void_t
导致了我的误解
尝试将std::void_t<Ts...>
视为“如果启用了所有Ts
,则启用”。