void_t“可以实现概念”?

我正在观看Walter Brown的CppCon2014谈话模板元编程的第二部分,在此期间他讨论了他的小说void_t<>构造的void_t<> 。 在他的演讲中,彼得·索莫拉德问他一个我不太明白的问题。 (链接直接回答问题,正在讨论的代码直接发生在那之前)

Sommerlad问道

沃尔特,这是否意味着我们现在可以实现概念lite?

沃尔特回应

哦耶! 我已经完成了…它没有完全相同的语法。

我理解这个交stream是关于概念精简版。 这种模式真的多才多艺的? 不pipe出于什么原因,我都没有看到它。 有人可以解释(或草图)这样的事情吗? 这仅仅是关于enable_if和定义特征,或者提问者提到的是什么?

void_t模板定义如下:

 template<class ...> using void_t = void; 

然后他使用它来检测types语句是否格式正确,使用它来实现is_copy_assignabletypes特征:

 //helper type template<class T> using copy_assignment_t = decltype(declval<T&>() = declval<T const&>()); //base case template template<class T, class=void> struct is_copy_assignable : std::false_type {}; //SFINAE version only for types where copy_assignment_t<T> is well-formed. template<class T> struct is_copy_assignable<T, void_t<copy_assignment_t<T>>> : std::is_same<copy_assignment_t<T>,T&> {}; 

由于谈话,我明白这个例子是如何工作的,但是我不明白我们从这里得到像Lite这样的东西。

是的,概念lite基本上装扮SFINAE。 此外,它允许更深入的反思,以便更好的超载。 然而,只有在概念谓词被定义为concept bool时才有效。 改进的重载不适用于当前的概念谓词,但可以使用条件重载。 让我们看看如何在C ++ 14中定义谓词,约束模板和重载函数。 这是很长的,但它覆盖了如何创build在C ++ 14中完成这些所需的所有工具。

定义谓词

首先,用所有的std::declvaldecltype处处读取谓词是很难看的。 相反,我们可以利用这个事实,即我们可以用一个尾随的decltype来限制一个函数(这里是Eric Niebler的博客文章),如下所示:

 struct Incrementable { template<class T> auto requires_(T&& x) -> decltype(++x); }; 

所以,如果++x无效, requires_成员函数就不可调用。 所以我们可以创build一个models特征来检查void_t是否可以使用void_t来调用:

 template<class Concept, class Enable=void> struct models : std::false_type {}; template<class Concept, class... Ts> struct models<Concept(Ts...), void_t< decltype(std::declval<Concept>().requires_(std::declval<Ts>()...)) >> : std::true_type {}; 

约束模板

所以当我们想要基于这个概念约束模板时,我们仍然需要使用enable_if ,但是我们可以使用这个macros来帮助它变得更加清晰:

 #define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0 

所以我们可以定义一个基于Incrementable概念约束的increment函数:

 template<class T, REQUIRES(models<Incrementable(T)>())> void increment(T& x) { ++x; } 

所以如果我们用increment的东西来调用Incrementable ,我们会得到这样的错误:

 test.cpp:23:5: error: no matching function for call to 'incrementable' incrementable(f); ^~~~~~~~~~~~~ test.cpp:11:19: note: candidate template ignored: disabled by 'enable_if' [with T = foo] template<class T, REQUIRES(models<Incrementable(T)>())> ^ 

重载函数

现在,如果我们要重载,我们要使用条件重载。 假设我们要使用概念谓词创build一个std::advance ,我们可以像这样定义它(现在我们将忽略可递减的情况):

 struct Incrementable { template<class T> auto requires_(T&& x) -> decltype(++x); }; struct Advanceable { template<class T, class I> auto requires_(T&& x, I&& i) -> decltype(x += i); }; template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())> void advance(Iterator& it, int n) { it += n; } template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())> void advance(Iterator& it, int n) { while (n--) ++it; } 

然而,这会导致一个模糊的重载(在概念lite中,当它和std::vector迭代器一起使用时,这仍然是一个模糊的重载,除非我们改变谓词来引用一个concept bool的其他谓词)。 我们要做的就是调用我们可以使用条件重载的方法。 它可以被认为是写这样的东西(这是不正确的C + +):

 template<class Iterator> void advance(Iterator& it, int n) if (models<Advanceable(Iterator, int)>()) { it += n; } else if (models<Incrementable(Iterator)>()) { while (n--) ++it; } 

所以如果第一个函数没有被调用,它会调用下一个函数。 所以让我们开始实现它的两个function。 我们将创build一个名为basic_conditional的类,它接受两个函数对象作为模板参数:

 struct Callable { template<class F, class... Ts> auto requires_(F&& f, Ts&&... xs) -> decltype( f(std::forward<Ts>(xs)...) ); }; template<class F1, class F2> struct basic_conditional { // We don't need to use a requires clause here because the trailing // `decltype` will constrain the template for us. template<class... Ts> auto operator()(Ts&&... xs) -> decltype(F1()(std::forward<Ts>(xs)...)) { return F1()(std::forward<Ts>(xs)...); } // Here we add a requires clause to make this function callable only if // `F1` is not callable. template<class... Ts, REQUIRES(!models<Callable(F1, Ts&&...)>())> auto operator()(Ts&&... xs) -> decltype(F2()(std::forward<Ts>(xs)...)) { return F2()(std::forward<Ts>(xs)...); } }; 

所以现在这意味着我们需要将我们的函数定义为函数对象:

 struct advance_advanceable { template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())> void operator()(Iterator& it, int n) const { it += n; } }; struct advance_incrementable { template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())> void operator()(Iterator& it, int n) const { while (n--) ++it; } }; static conditional<advance_advanceable, advance_incrementable> advance = {}; 

所以现在如果我们尝试使用它与一个std::vector

 std::vector<int> v = { 1, 2, 3, 4, 5, 6 }; auto iterator = v.begin(); advance(iterator, 4); std::cout << *iterator << std::endl; 

它将编译和打印出5

但是, std::advance实际上有三个重载,所以我们可以使用basic_conditional来实现conditional ,这个conditional可以用于recursion的任意数量的函数:

 template<class F, class... Fs> struct conditional : basic_conditional<F, conditional<Fs...>> {}; template<class F> struct conditional<F> : F {}; 

所以,现在我们可以像这样编写完整的std::advance

 struct Incrementable { template<class T> auto requires_(T&& x) -> decltype(++x); }; struct Decrementable { template<class T> auto requires_(T&& x) -> decltype(--x); }; struct Advanceable { template<class T, class I> auto requires_(T&& x, I&& i) -> decltype(x += i); }; struct advance_advanceable { template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())> void operator()(Iterator& it, int n) const { it += n; } }; struct advance_decrementable { template<class Iterator, REQUIRES(models<Decrementable(Iterator)>())> void operator()(Iterator& it, int n) const { if (n > 0) while (n--) ++it; else { n *= -1; while (n--) --it; } } }; struct advance_incrementable { template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())> void operator()(Iterator& it, int n) const { while (n--) ++it; } }; static conditional<advance_advanceable, advance_decrementable, advance_incrementable> advance = {}; 

超载与兰姆达斯

不过,另外,我们可以使用lambdas来代替函数对象,这样可以使得它更易于编写。 所以我们使用这个STATIC_LAMBDAmacros在编译时构造lambdaexpression式:

 struct wrapper_factor { template<class F> constexpr wrapper<F> operator += (F*) { return {}; } }; struct addr_add { template<class T> friend typename std::remove_reference<T>::type *operator+(addr_add, T &&t) { return &t; } }; #define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + [] 

并添加一个make_conditional函数是constexpr

 template<class... Fs> constexpr conditional<Fs...> make_conditional(Fs...) { return {}; } 

那么我们现在可以这样写advancefunction:

 constexpr const advance = make_conditional( STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Advanceable(decltype(it), int)>())) { it += n; }, STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Decrementable(decltype(it))>())) { if (n > 0) while (n--) ++it; else { n *= -1; while (n--) --it; } }, STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Incrementable(decltype(it))>())) { while (n--) ++it; } ); 

这比使用函数对象版本更紧凑和可读。

另外,我们可以定义一个modeled函数来减lessdecltype ugliness:

 template<class Concept, class... Ts> constexpr auto modeled(Ts&&...) { return models<Concept(Ts...)>(); } constexpr const advance = make_conditional( STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Advanceable>(it, n))) { it += n; }, STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Decrementable>(it))) { if (n > 0) while (n--) ++it; else { n *= -1; while (n--) --it; } }, STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Incrementable>(it))) { while (n--) ++it; } ); 

最后,如果你有兴趣使用现有的图书馆解决scheme(而不是像我所展示的那样自己动手)。 Tick库提供了定义概念和约束模板的框架。 Fit库可以处理函数和重载。