void_t“可以实现概念”吗?


68

我正在观看Walter Brown在CppCon2014上有关模板元编程的第二部分,在此期间他讨论了他的新颖void_t<>结构的用法。彼得·索默拉德(Peter 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。


2
有趣的是,不幸的是,它与“请看一下托管在(某些存储库)上的我的源代码”并没有什么不同。更糟糕的是,因为它是视频。您能否在问题中添加相关的代码段,使其变得更独立?至少要在您要谈论的视频中指定时间,“第二部分”几乎没有用。
Ben Voigt 2014年

1
@BenVoigt好的,我更新了问题。youtube链接中有一个时间码,可直接跳至该问题。我还添加了他在演示中使用的主要示例。我希望这一问题更加明确。
Tim Seguine 2014年

4
您无法从那里到达Concepts Lite。这是一项很酷的技术,您可以将其用于模板参数列表中的漂亮“ requires”子句,但是您无法让编译器对模板进行部分排序,基于该模板可以对“ concept”进行更细化。您也不能对类模板的非模板成员函数或Concepts TS的其他一些全新语言功能施加要求。
Jonathan Wakely 2014年

1
@JonathanWakely好的,您所说的这些“ requires”子句(我们的假冒子句)只是修饰enable_if得更易于访问且易于编写的约束和类型特征,还是更复杂?
Tim Seguine 2014年

4
是的,我相信这是做SFINAE的一种很酷的方法。Concepts Lite比SFINAE丰富得多,它完全替代了SFINAE,而不是使用漂亮的语法进行修饰。
Jonathan Wakely 2014年

Answers:


118

是的,lite的概念基本上是在打扮SFINAE。另外,它还可以进行更深入的自省,以实现更好的重载。但是,仅当概念谓词定义为时,该方法才有效concept bool。改进的重载不适用于当前概念谓词,但可以使用条件重载。让我们看看如何在C ++ 14中定义谓词,约束模板和重载函数。这有点长,但是它涵盖了如何创建在C ++ 14中完成此操作所需的所有工具。

定义谓词

首先,它是样的丑陋与所有的读谓词std::declvaldecltype无处不在。相反,我们可以利用以下事实:可以使用尾随的decltype约束函数(来自Eric Niebler的博客此处),如下所示:

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

因此,我们可以定义一个incrementIncrementable概念约束的函数:

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

因此,如果我们increment使用not进行调用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 boolstd::vector迭代器一起使用,否则除非我们将谓词更改为引用a中的其他谓词,否则这仍将是模棱两可的重载)。我们想要做的是对调用进行排序,我们可以使用条件重载来完成。可以考虑编写这样的内容(无效的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重载

但是,此外,我们可以使用lambda代替函数对象来编写它,这可以帮助使其更清晰地编写。因此,我们STATIC_LAMBDA在编译时使用此宏来构造lambda:

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

最后,如果您对使用现有的库解决方案感兴趣(而不是像我展示的那样滚动自己的库解决方案)。还有就是库,提供定义概念和约束模板的框架。而飞度库可以处理的功能和超载。


2
感谢您抽出宝贵时间写出如此广泛的答案。这超出了我的预期,但正是我所希望的那种答案。
Tim Seguine 2014年

1
由于高质量和高数量而被投票赞成...但是OMG我的C ++ 11恶心开始发作。–
Jason S

5
FWIW,用于实现STATIC_LAMBDA的技巧不是符合标准的C ++。它可以在实践中使用,因为系统必须要很奇怪才能不起作用,但这并不能使其成为100%的犹太洁食。
Charphacy

1
@mattnewport是,它与函数类型Ts...匹配,其中匹配函数参数并Concept匹配函数类型的返回类型。
Paul Fultz II 2014年

2
嗯,好的,我想我明白了。令我震惊的是,没有看到具有该签名的函数从何而来,但实际上从来没有这样的函数,这只是一种便捷的语法,可以对类型进行模式匹配。聪明。
mattnewport 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.