void_t“可以实现概念”? [英] void_t "can implement concepts"?
问题描述
我正在观看 Walter Brown的CppCon2014的第二部分谈论模板元编程,在此期间他讨论了他的小说 void_t<< / code>构造的用法。在他的演讲期间,Peter Sommerlad问他一个我不太明白的问题。 (链接直接转到问题,正在讨论的代码直接在此之前发生)
I was watching the second part of Walter Brown's CppCon2014 talk on template metaprogramming, during which he discussed the uses of his novel void_t<>
construction. During his presentation Peter Sommerlad asked him a question that I didn't quite understand. (link goes directly to the question, the code under discussion took place directly before that)
Sommerlad问
Sommerlad asked
Walter,这是否意味着我们可以实现现在的概念?
Walter, would that mean we actually can implement concepts lite right now?
p>
to which Walter responded
哦!我已经做了...它没有完全相同的语法。
Oh yeah! I've done it ... It doesn't have quite the same syntax.
我理解这个交流是关于Concepts Lite 。这种模式真的是多才多艺的吗?无论什么原因,我都没有看到它。有人可以解释(或草图)这样的东西可能看起来吗?这是关于 enable_if
并定义traits,或者提问者指的是什么?
I understood this exchange to be about Concepts Lite. Is this pattern really that versatile? For whatever reason, I am not seeing it. Can someone explain (or sketch) how something like this might look? Is this just about enable_if
and defining traits, or what was the questioner referring to?
void_t
模板定义如下:
template<class ...> using void_t = void;
然后,他使用这个来检测类型语句是否形成,使用它来实现 is_copy_assignable
type trait:
He uses this then to detect if type statements are well formed, using this to implement the is_copy_assignable
type trait:
//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&> {};
由于谈话,我理解这个例子如何工作,
Because of the talk, I understand how this example works, but I don't see how we get from here to something like Concepts Lite.
推荐答案
是的,概念lite基本上修饰SFINAE。此外,它允许更深入的内省,以允许更好的超载。然而,只有当概念谓词被定义为 concept bool
时,它才有效。改进的重载对当前概念谓词不起作用,但是可以使用条件重载。让我们看看我们如何在C ++ 14中定义谓词,约束模板和重载函数。这是一个很长的时间,但它是如何创建所有的工具所需的完成这个在C ++ 14。
Yes, concepts lite basically dresses up SFINAE. Plus it allows deeper introspection to allow for better overloading. However that only works if the concept predicates are defined as concept bool
. The improved overloading does not work with the current concept predicates, but conditional overloading can be used. Lets look how we can define predicates, constrain templates, and overload functions in C++14. This is kind of long, but it goes over how to create all of the tools needed to accomplish this in C++14.
首先,使用所有 std :: declval
和 decltype > code>无处不在。相反,我们可以利用这样的事实,我们可以使用尾随的decltype来限制函数(来自Eric Niebler的博客此处),如下所示:
First, it is kind of ugly to read the predicate with all the std::declval
and decltype
everywhere. Instead, we can take advantage of the fact that we can constrain a function using a trailing decltype(from Eric Niebler’s blog post here), like this:
struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};
因此,如果 ++ x
,那么需要_
成员函数是不可调用的。所以我们可以创建一个 models
trait,只是检查是否需要_
是可调用的 void_t
:
So if ++x
is not valid, then the requires_
member function is not callable. So we can create a models
trait that just checks if requires_
is callable using 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
,但是我们可以使用这个宏来帮助使它更清晰:
Constraining Templates
So when we want to constrain the template based on the concept, we will still need to use enable_if
, but we can use this macro to help make it cleaner:
#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0
因此,我们可以定义一个 increment
函数, >可增加的概念:
So we can define an increment
function that is constrained based on Incrementable
concept:
template<class T, REQUIRES(models<Incrementable(T)>())>
void increment(T& x)
{
++x;
}
因此,如果我们调用 increment
Incrementable
,我们将得到这样的错误:
So if we call increment
with something that is not Incrementable
, we will get an error like this:
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)>())>
^
重载函数
现在如果我们想做重载,我们想使用条件重载。假设我们要创建 std :: advance
使用概念谓词,我们可以这样定义它(现在我们将忽略可递减的情况):
Overloading Functions
Now if we want to do overloading, we want to use conditional overloading. Say we want to create an std::advance
using concept predicates, we could define it like this(for now we will ignore the decrementable case):
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;
}
但是,这会导致模糊的重载除非我们改变我们的谓词以引用概念bool
中的其他谓词),当它与 std :: vector
iterator。我们想要做的是排序调用,我们可以使用条件重载。它可以被认为写这样的东西(这是无效的C ++):
However, this causes an ambiguous overload(In concepts lite this would still be an ambiguous overload unless we change our predicates to refer to the other predicates in a concept bool
) when its used with std::vector
iterator. What we want to do is order the calls, which we can do using conditional overloading. It can be thought of writing something like this(which is not valid 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;
}
所以如果第一个函数没有被调用,它会调用下一个函数。所以让我们开始为两个函数实现它。我们将创建一个名为 basic_conditional
的类,它接受两个函数对象作为模板参数:
So if the first function isn't called, it will call the next function. So lets start by implementing it for two functions. We will create a class called basic_conditional
which accepts two function objects as template parameters:
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)...);
}
};
现在这意味着我们需要将函数定义为函数对象:
So now that means we need to define our functions as functions objects instead:
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
:
So now if we try to use it with an 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
来实现条件
,它适用于使用递归的任意数量的函数:
However, std::advance
actually has three overloads, so we can use the basic_conditional
to implement conditional
that works for any number of functions using recursion:
template<class F, class... Fs>
struct conditional : basic_conditional<F, conditional<Fs...>>
{};
template<class F>
struct conditional<F> : F
{};
现在我们可以写完整的 std :: advance
这样:
So, now we can write the full std::advance
like this:
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重载
,我们可以使用lambdas来写它,而不是函数对象,这可以帮助使它更清洁写。因此,我们使用此 STATIC_LAMBDA
宏在编译时构造lambdas:
Overloading With Lambdas
However, additionally, we could use lambdas to write it instead of function objects which can help make it cleaner to write. So we use this STATIC_LAMBDA
macro to construct lambdas at compile time:
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
这样的函数:
Then we can now write the advance
function like this:
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;
}
);
这比使用函数对象版本更简洁和可读。
Which is little more compact and readable than using the function object versions.
此外,我们可以定义一个模型
函数来减少 decltype
/ p>
Additionally, we could define a modeled
function to reduce down the decltype
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;
}
);
最后,如果您有兴趣使用现有的库解决方案显示)。有 Tick 库提供了定义概念和约束模板的框架。而且 Fit 库可以处理函数和重载。
Finally, if you are interested in using existing library solutions(rather than rolling your own like I've shown). There is the Tick library that provides a framework for defining concepts and constraining templates. And the Fit library can handle the functions and overloading.
这篇关于void_t“可以实现概念”?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!