如何简化模板模板参数中的 enable_if 别名

How to simplify enable_if alias in template template parameter

我的目标是拥有一个结构,它接受一个专门的 enable_if_t<> 的别名以及一个类型名可变参数包,然后告诉我是否满足所有 enable_if 的条件包中的类型。我有一堆这样的专用 enable_if,但在我们将它们放入我们的开源项目之前需要为它们编写测试。我有大约 2000 多行代码手动测试这些专业化,但我敢打赌,如果我能弄清楚下面的模式,我可以把它写到 100 或 200 行。我有一个工作版本(+ godbolt link),但我不确定它为什么工作并且在实现接收参数包的情况下该方案被打破

这是我想编写的代码示例及其结果。我正在使用 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

下面确实做了我想要的,但我不太明白怎么做。

// 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_typeimpl 部分专业化需要 void_t。

我想要得到的是一个 require_variadic_tester,它接受可变模板化别名,类似于 enable_if<conjunction<check<T...>>::value>,并给出真或假。可悲的是,下面的 returns 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 导致了我的误解

下面是神栓,非常感谢任何帮助理解这一点的人!

https://godbolt.org/z/8XNqpo

编辑:

为了提供更多背景信息,说明为什么我要在 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 仅 mini-library 我正在调用 "bad fake concepts in 14"(或简称 bfc14 ;-))

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的偏特化,否则不会有偏特化发现,因为任何偏特化的第三个参数都会失败。

这就是 void_t 发挥作用的地方。由于您想将 Check 注入参数,但您需要 void,这时 void_t 就派上用场了,因为用于特化的每种类型都映射到 void,这是你真正需要的。当部分特化没有失败时,您将有两个启用的特化,默认的和部分的。

最终会选择部分的,因为它比另一个更专业,因为 void 的计算方式依赖于其他模板参数。

这是第一部分。对于第二部分(可变参数模板),请记住,如果 enable_if 成功,它 returns 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的别名,returns 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 失败,则仅编译第二个重载,即 returns false。但是,如果检查有效并且定义了内部 enable_if,那么两个重载都将有效,但是,因为您已经传递了一个 int (42),并且第二个重载收到一个unsigned,第一个重载将是一个更好的匹配,返回 true.

https://coliru.stacked-crooked.com/a/bfe22ea099dd5749

最后,如果您希望检查始终是 true_typefalse_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>;

不确定是否了解您的所有需求,但是...

What I would like to get to is a require_variadic_tester that takes in a variadic templated alias, something like enable_if<conjunction<check<T...>>::value>, and gives me true or false. Sadly, the below returns false no matter what types come in

你确定要 conjunction<check<T...>> 吗?

或者你想要conjunction<check<T>...>

我的意思是...检查必须接收类型的可变列表,或者您想检查一个别名(如您的示例)接收一个类型和一个为真的连词当且仅当(如果和只有当)所有类型的检查都满足时?

在第二种情况下,std::void_t 可以很方便地验证是否满足所有检查。

我提出以下require_variadic_implrequire_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

think my failure to understand void_t in the first meta function is causing my misunderstanding

试着把 std::void_t<Ts...> 想成 "enable if all Ts are enabled"。

这是您的 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_arithmeticT = 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_arithmeticT1 = double。类型 void_t<Check<T1>> 不会导致 CheckT1 的任何推导。但是推导的参数值必须代入,我们发现void_t<Check<T1>>void_t<require_arithmetic<double>>void。这确实与模板参数中的 void 匹配,因此偏特化确实匹配,并且 require_template_impl<require_arithmetic, double, void> 继承了 std::true_type,而不是 std::false_type.

另一方面,如果 T1std::string 而不是 double,则通过最终的 enable_if<...>::type 其中不存在成员 type。当将推导的模板参数替换为其他模板参数失败时,这意味着部分特化会因为不匹配而被丢弃。所以 require_template_impl<require_arithmetic, std::string, void> 使用主模板并继承 std::false_type.

回到require_testervalue成员,它递归地找到require_tester<require_arithmetic, double, int>::value via require_tester<require_arithmetic, int>::value via require_tester<require_arithmetic>::value 与[=54]相同=].所有 value 成员都为真,所以最后的 value 为真。

不过我会稍微简化一下:

  1. voidrequire_tester 递归中是不必要的,并导致奇怪的 "fact" require_tester<Anything, void>::value 始终为真。最好从主要 require_tester 模板中删除 = void 默认值,并改为使用基本情况 template <template <class> class Check> require_tester<Check>

  2. 你在 require_tester 主模板中的 value 表达式总是恰好给 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,得到了想要的结果。