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

标签 c++ c++14 c++17 variadic-templates sfinae

我的目标是拥有一个接受特殊化 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,非常感谢任何帮助理解这一点的人!

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 迷你库,用于我所说的“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_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 , 将推导的模板参数替换为 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>::valuerequire_tester<require_arithmetic, void>::value 相同.所有 value成员是真实的,所以最后value是真的。

虽然我会稍微简化一下:

  1. voidrequire_tester 中是不必要的递归,并导致奇怪的“事实”require_tester<Anything, void>::value总是正确的。最好删除 = void默认来自主 require_tester模板,并制作基本案例 template <template <class> class Check> require_tester<Check>相反。

  2. 你的 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/

相关文章:

c++ - 模板无法将函数定义与现有声明相匹配

c++ - 如何 static_assert 初始化列表是一定大小

c++ - `nullopt` 可以作为非类型模板参数传递吗?

c++ - 如果 lambda 使用 std::move() 捕获不可复制的对象,为什么它是不可 move 的?

c++ - 什么是 `std::reinterpret_pointer_cast`,什么时候应该使用它?

c++ - 如何访问模板包参数的模板参数

c++ - 如何在 visual studio(c++) 中将文字中文字符串分配给 wchar_t*?

c++ - 使用 OpenGL 从文本文件绘制图像

c++ - std::thread 不是使用 Eclipse Kepler MinGW 的命名空间 std 的成员

c++ - 编译错误,对成员变量进行多重赋值的方法,模板类 C++