简体   繁体   English

void_t“可以实现概念”吗?

[英]void_t “can implement concepts”?

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. 我正在观看Walter Brown的CppCon2014关于模板元编程的第二部分,在此期间他讨论了他的小说void_t<>构造的void_t<> During his presentation Peter Sommerlad asked him a question that I didn't quite understand. 在他的演讲中,Peter Sommerlad问他一个我不太明白的问题。 (link goes directly to the question, the code under discussion took place directly before that) (链接直接转到问题,正在讨论的代码直接发生在那之前)

Sommerlad asked 索默拉德问道

Walter, would that mean we actually can implement concepts lite right now? 沃尔特,这是否意味着我们现在实际上可以实现概念精简版?

to which Walter responded 沃尔特回应了什么

Oh yeah! 哦耶! I've done it ... It doesn't have quite the same syntax. 我已经完成了......它没有完全相同的语法。

I understood this exchange to be about Concepts Lite. 我理解这个交换是关于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? 这只是关于enable_if和定义特征,还是提问者指的是什么?

The void_t template is defined as follows: void_t模板定义如下:

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

He uses this then to detect if type statements are well formed, using this to implement the is_copy_assignable type trait: 然后他使用它来检测类型语句是否格式正确,使用它来实现is_copy_assignable类型特征:

//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. 由于这个话题,我理解这个例子是如何工作的,但是我没有看到我们如何从这里得到像Concepts Lite这样的东西。

Yes, concepts lite basically dresses up SFINAE. 是的,概念精简版基本上打扮成SFINAE。 Plus it allows deeper introspection to allow for better overloading. 此外,它允许更深入的内省,以实现更好的重载。 However that only works if the concept predicates are defined as concept bool . 但是,只有在概念谓词被定义为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. 让我们看看如何在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. 这有点长,但它讨论了如何在C ++ 14中创建完成此任务所需的所有工具。

Defining Predicates 定义谓词

First, it is kind of ugly to read the predicate with all the std::declval and decltype everywhere. 首先,用所有std::declvaldecltype读取谓词到处都是std::declval 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: 取而代之的是,我们可以采取的事实,即我们可以使用尾随decltype(埃里克Niebler的博客文章约束的功能在这里 ),就像这样:

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

So if ++x is not valid, then the requires_ member function is not callable. 因此,如果++x无效,则requires_ member函数不可调用。 So we can create a models trait that just checks if requires_ is callable using void_t : 因此,我们可以创建一个models特征,只需使用void_t检查requires_是否可调用:

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
{};

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: 因此,当我们想要基于概念约束模板时,我们仍然需要使用enable_if ,但我们可以使用此宏来帮助使其更清晰:

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

So we can define an increment function that is constrained based on Incrementable concept: 所以我们可以定义一个基于Incrementable概念约束的increment函数:

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

So if we call increment with something that is not Incrementable , we will get an error like this: 所以,如果我们称之为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)>())>
                  ^

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): 假设我们想要使用概念谓词创建一个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;
}

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. 但是,当它与std::vector迭代器一起使用时,这会导致模糊的重载(在概念lite中,这仍然是一个模糊的重载,除非我们将谓词更改为引用concept bool的其他谓词)。 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++): 可以想到写这样的东西(这是无效的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;
}

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: 我们将创建一个名为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)...);
    }
};

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 = {};

So now if we try to use it with an std::vector : 所以现在如果我们尝试将它与std::vector

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

It will compile and print out 5 . 它将编译并打印5

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: 但是, std::advance实际上有三个重载,所以我们可以使用basic_conditional来实现使用递归适用于任意数量函数的conditional

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

template<class F>
struct conditional<F> : F
{};

So, now we can write the full std::advance like this: 所以,现在我们可以像这样编写完整的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 = {};

Overloading With Lambdas 使用Lambdas重载

However, additionally, we could use lambdas to write it instead of function objects which can help make it cleaner to write. 但是,另外,我们可以使用lambdas来编写它而不是函数对象,这有助于使写入更清晰。 So we use this STATIC_LAMBDA macro to construct lambdas at compile time: 所以我们使用这个STATIC_LAMBDA宏在编译时构造lambdas:

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() + []

And add a make_conditional function that is constexpr : 并添加一个make_conditional函数constexpr

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

Then we can now write the advance function like this: 然后我们现在可以像这样编写advance函数:

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. 这比使用函数对象版本更紧凑和可读。

Additionally, we could define a modeled function to reduce down the decltype ugliness: 另外,我们可以定义一个modeled函数来减少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;
    }
);

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. Tick库提供了用于定义概念和约束模板的框架。 And the Fit library can handle the functions and overloading. Fit库可以处理函数和重载。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM