簡體   English   中英

void_t“可以實現概念”嗎?

[英]void_t “can implement concepts”?

我正在觀看Walter Brown的CppCon2014關於模板元編程的第二部分,在此期間他討論了他的小說void_t<>構造的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這樣的東西。

是的,概念精簡版基本上打扮成SFINAE。 此外,它允許更深入的內省,以實現更好的重載。 但是,只有在概念謂詞被定義為concept bool時才有效。 改進的重載不適用於當前的概念謂詞,但可以使用條件重載。 讓我們看看如何在C ++ 14中定義謂詞,約束模板和重載函數。 這有點長,但它討論了如何在C ++ 14中創建完成此任務所需的所有工具。

定義謂詞

首先,用所有std::declvaldecltype讀取謂詞到處都是std::declval 取而代之的是,我們可以采取的事實,即我們可以使用尾隨decltype(埃里克Niebler的博客文章約束的功能在這里 ),就像這樣:

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

因此,如果++x無效,則requires_ member函數不可調用。 因此,我們可以創建一個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
{};

約束模板

因此,當我們想要基於概念約束模板時,我們仍然需要使用enable_if ,但我們可以使用此宏來幫助使其更清晰:

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

所以我們可以定義一個基於Incrementable概念約束的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;
}

但是,當它與std::vector迭代器一起使用時,這會導致模糊的重載(在概念lite中,這仍然是一個模糊的重載,除非我們將謂詞更改為引用concept bool的其他謂詞)。 我們想要做的是訂購調用,我們可以使用條件重載來完成調用。 可以想到寫這樣的東西(這是無效的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 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庫可以處理函數和重載。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM