void_t"可以实现概念"?

时间:2014-10-22 17:07:01

标签: c++ templates c++11 template-meta-programming c++-concepts

我正在观看Walter Brown's CppCon2014 talk on template metaprogramming的第二部分,在此期间他讨论了他的小说void_t<>构造的用法。在他的演讲中,Peter Sommerlad向他提出了一个我不太了解的问题。 (链接直接涉及问题,正在讨论的代码直接发生在那之前)

Sommerlad问道

  沃尔特,这是否意味着我们现在实际上可以实现概念精简版?

沃尔特回答

  噢,是的!我已经完成了......它没有完全相同的语法。

我理解这次交流是关于Concepts Lite的。这种模式真的 多才多艺?无论出于何种原因,我都没有看到它。有人可以解释(或描绘)这样的事情会是什么样子?这只是enable_if和定义特征,或提问者指的是什么?

void_t模板定义如下:

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

然后他使用它来检测类型语句是否格式正确,使用它来实现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&> {};

由于这次谈话,我理解这个例子是如何运作的,但是我不知道我们是如何从这里得到像Concepts Lite这样的东西。

1 个答案:

答案 0 :(得分:115)

是的,概念精简版基本上打扮成SFINAE。此外,它允许更深入的内省,以实现更好的重载。但是,只有在概念谓词定义为concept bool时才有效。改进的重载不适用于当前的概念谓词,但可以使用条件重载。让我们看看如何在C ++ 14中定义谓词,约束模板和重载函数。这有点长,但它讨论了如何在C ++ 14中创建完成此任务所需的所有工具。

定义谓词

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

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

因此,如果++x无效,则requires_成员函数不可调用。因此,我们可以使用models创建requires_特征,只检查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,但我们可以使用此宏来帮助使其更清晰:

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

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

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)>())>
                  ^

重载函数

现在,如果我们想要进行重载,我们希望使用条件重载。假设我们想要使用概念谓词创建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;
}

然而,这导致了一个模糊的重载(在概念中,这仍然是一个模糊的重载,除非我们更改我们的谓词以引用concept bool中的其他谓词)当它与std::vector迭代器一起使用时。我们想要做的是订购调用,我们可以使用条件重载来完成调用。可以想到写这样的东西(这不是有效的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的类,它接受两个函数对象作为模板参数:

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,它适用于使用递归的任意数量的函数:

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重载

然而,另外,我们可以使用lambdas来编写它而不是函数对象,这有助于使写入更清晰。所以我们使用这个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() + []

添加make_conditional constexpr函数:template<class... Fs> constexpr conditional<Fs...> make_conditional(Fs...) { return {}; }

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; } ); 函数:

modeled

与使用函数对象版本相比,它更加紧凑和易读。

此外,我们可以定义decltype函数来减少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; } ); 丑陋:

{{1}}

最后,如果您对使用现有的库解决方案感兴趣(而不是像我所示的那样滚动自己的解决方案)。有Tick库提供了用于定义概念和约束模板的框架。 Fit库可以处理函数和重载。