void_t“可以实现概念”? [英] void_t "can implement concepts"?

查看:211
本文介绍了void_t“可以实现概念”?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在观看 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆