void t "может реализовать концепции"?


Я смотрел вторую часть выступление Уолтера Брауна CppCon2014 по шаблонному метапрограммированию, во время которого он обсуждал использование своего романа void_t<> строительство. Во время своего выступления Питер Соммерлад задал ему вопрос, который я не совсем понял. (ссылка идет непосредственно на вопрос, обсуждаемый код имел место непосредственно перед этим)

Sommerlad попросил

Уолтер, это означает, что мы действительно можем реализовать концепции lite прямо сейчас?

на что Уолтер ответил

О да! Я сделал это ... Это не совсем тот же синтаксис.

я понял, что этот обмен был о понятиях Lite. Действительно ли это шаблон это универсальный? По какой-то причине я этого не вижу. Может ли кто-нибудь объяснить (или нарисовать), как это может выглядеть? Это просто о enable_if и определяющие черты, или каков был вопрошающий имеете в виду?

The 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 64

1 ответ:

да, concepts lite в основном одевается SFINAE. Кроме того, это позволяет глубже самоанализ, чтобы обеспечить лучшую перегрузку. Однако это работает только в том случае, если предикаты понятия определены как concept bool. Улучшенная перегрузка не работает с текущими предикатами концепта, но может использоваться условная перегрузка. Давайте посмотрим, как мы можем определять предикаты, ограничивать шаблоны и перегружать функции в C++14. Это довольно долго, но он идет над тем, как создать все инструменты, необходимые для достичь этого в C++14.

Определение Предикатов

во-первых, это своего рода некрасиво читать предикат со всеми std::declval и decltype везде. Вместо этого мы можем воспользоваться тем фактом, что мы можем ограничить функцию, используя конечный decltype(из сообщения в блоге Эрика ниблера здесь), как это:

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 функция, которая ограничена на основе :

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

однако это вызывает неоднозначную перегрузку(в concepts lite это все равно будет неоднозначная перегрузка, если мы не изменим наши предикаты для ссылки другие предикаты в 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 = {};

Перегрузка Лямбдами

однако, кроме того, мы могли бы использовать лямбды для его записи вместо объектов функций, которые могут помочь сделать его более чистым для записи. Поэтому мы используем это STATIC_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() + []

и добавить