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_assignable
types特征:
//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::declval
和decltype
处处读取谓词是很难看的。 相反,我们可以利用这个事实,即我们可以用一个尾随的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_LAMBDA
macros在编译时构造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 {}; }
那么我们现在可以这样写advance
function:
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库可以处理函数和重载。