問題描述
我正在觀看 Walter Brown 的 CppCon2014 的第二部分談?wù)撃0逶幊?,在此期間,他討論了他新穎的void_t<>
構(gòu)造的使用.在他的演講中,Peter Sommerlad 問了他一個(gè)我不太明白的問題.(鏈接直接指向問題,討論中的代碼直接發(fā)生在此之前)
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. During his presentation Peter Sommerlad asked him a question that I didn't quite understand. (link goes directly to the question, the code under discussion took place directly before that)
薩默拉德問道
Walter,這是否意味著我們現(xiàn)在實(shí)際上可以實(shí)現(xiàn)精簡版的概念?
Walter, would that mean we actually can implement concepts lite right now?
沃爾特回應(yīng)了
哦耶!我已經(jīng)完成了……它的語法不太一樣.
Oh yeah! I've done it ... It doesn't have quite the same syntax.
我理解這次交流是關(guān)于 Concepts Lite.這種模式真的那個(gè)通用嗎?無論出于何種原因,我都沒有看到它.有人可以解釋(或草圖)這樣的東西會是什么樣子嗎?這只是關(guān)于 enable_if
和定義特征,還是提問者指的是什么?
I understood this exchange to be about 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?
void_t
模板定義如下:
template<class ...> using void_t = void;
他使用 then 來檢測類型語句是否格式正確,并使用它來實(shí)現(xiàn) is_copy_assignable
類型特征:
He uses this then to detect if type statements are well formed, using this to implement the is_copy_assignable
type trait:
//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&> {};
因?yàn)檎勗挘颐靼走@個(gè)例子是如何工作的,但我不明白我們?nèi)绾螐倪@里得到像 Concepts Lite 這樣的東西.
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 基本上裝扮了 SFINAE.此外,它還允許進(jìn)行更深入的內(nèi)省,以實(shí)現(xiàn)更好的重載.然而,這僅在概念謂詞被定義為 concept bool
時(shí)才有效.改進(jìn)的重載不適用于當(dāng)前的概念謂詞,但可以使用條件重載.讓我們看看如何在 C++14 中定義謂詞、約束模板和重載函數(shù).這有點(diǎn)長,但它介紹了如何在 C++14 中創(chuàng)建完成此任務(wù)所需的所有工具.
Yes, concepts lite basically dresses up SFINAE. Plus it allows deeper introspection to allow for better overloading. However that only works if the concept predicates are defined as 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. This is kind of long, but it goes over how to create all of the tools needed to accomplish this in C++14.
首先,閱讀帶有所有 std::declval
和 decltype
的謂詞有點(diǎn)難看.相反,我們可以利用這樣一個(gè)事實(shí),即我們可以使用尾隨 decltype 來約束函數(shù)(來自 Eric Niebler 的博客文章 這里),像這樣:
First, it is kind of ugly to read the predicate with all the std::declval
and decltype
everywhere. 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:
struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};
因此,如果 ++x
無效,則 requires_
成員函數(shù)不可調(diào)用.所以我們可以創(chuàng)建一個(gè) models
trait 來檢查 requires_
是否可以使用 void_t
調(diào)用:
So if ++x
is not valid, then the requires_
member function is not callable. So we can create a models
trait that just checks if requires_
is callable using 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
{};
約束模板
所以當(dāng)我們想基于概念約束模板時(shí),我們?nèi)匀恍枰褂?code>enable_if,但我們可以使用這個(gè)宏來幫助使其更清晰:
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:
#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0
所以我們可以定義一個(gè)基于Incrementable
概念約束的increment
函數(shù):
So we can define an increment
function that is constrained based on Incrementable
concept:
template<class T, REQUIRES(models<Incrementable(T)>())>
void increment(T& x)
{
++x;
}
所以如果我們用不是Incrementable
的東西調(diào)用increment
,我們會得到這樣的錯(cuò)誤:
So if we call increment
with something that is not Incrementable
, we will get an error like this:
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)>())>
^
重載函數(shù)
現(xiàn)在如果我們要做重載,我們要使用條件重載.假設(shè)我們要創(chuàng)建一個(gè) std::advance
使用概念謂詞,我們可以這樣定義它(現(xiàn)在我們將忽略可遞減的情況):
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):
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;
}
然而,當(dāng)它與 std::vector
迭代器.我們想要做的是對調(diào)用進(jìn)行排序,我們可以使用條件重載來完成.可以考慮寫這樣的東西(這不是有效的 C++):
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. 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++):
template<class Iterator>
void advance(Iterator& it, int n) if (models<Advanceable(Iterator, int)>())
{
it += n;
}
else if (models<Incrementable(Iterator)>())
{
while (n--) ++it;
}
所以如果第一個(gè)函數(shù)沒有被調(diào)用,它將調(diào)用下一個(gè)函數(shù).因此,讓我們從為兩個(gè)功能實(shí)現(xiàn)它開始.我們將創(chuàng)建一個(gè)名為 basic_conditional
的類,它接受兩個(gè)函數(shù)對象作為模板參數(shù):
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:
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)...);
}
};
所以現(xiàn)在這意味著我們需要將我們的函數(shù)定義為函數(shù)對象:
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 = {};
所以現(xiàn)在如果我們嘗試將它與 std::vector
一起使用:
So now if we try to use it with an 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
實(shí)際上有三個(gè)重載,所以我們可以使用 basic_conditional
來實(shí)現(xiàn)適用于任意數(shù)量的 conditional
使用遞歸的函數(shù):
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:
template<class F, class... Fs>
struct conditional : basic_conditional<F, conditional<Fs...>>
{};
template<class F>
struct conditional<F> : F
{};
所以,現(xiàn)在我們可以像這樣編寫完整的std::advance
:
So, now we can write the full std::advance
like this:
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 = {};
Lambda 重載
然而,此外,我們可以使用 lambdas 代替函數(shù)對象來編寫它,這有助于使其編寫起來更清晰.所以我們使用這個(gè) STATIC_LAMBDA
宏來在編譯時(shí)構(gòu)造 lambdas:
Overloading With Lambdas
However, additionally, we could use lambdas to write it instead of function objects which can help make it cleaner to write. So we use this STATIC_LAMBDA
macro to construct lambdas at compile time:
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() + []
并添加一個(gè)make_conditional
函數(shù),即constexpr
:
template<class... Fs>
constexpr conditional<Fs...> make_conditional(Fs...)
{
return {};
}
那么我們現(xiàn)在可以像這樣編寫advance
函數(shù):
Then we can now write the advance
function like this:
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;
}
);
這比使用函數(shù)對象版本更緊湊和可讀.
Which is little more compact and readable than using the function object versions.
另外,我們可以定義一個(gè)modeled
函數(shù)來減少decltype
的丑陋:
Additionally, we could define a modeled
function to reduce down the 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;
}
);
最后,如果您有興趣使用現(xiàn)有的庫解決方案(而不是像我展示的那樣滾動自己的解決方案).Tick 庫提供了一個(gè)定義概念和約束模板的框架.而 Fit 庫可以處理函數(shù)和重載.
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. And the Fit library can handle the functions and overloading.
這篇關(guān)于void_t“可以實(shí)現(xiàn)概念"?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網(wǎng)!