問題描述
我正在觀看 Walter Brown 的 CppCon2014 的第二部分談論模板元編程,在此期間,他討論了他新穎的void_t<>
構造的使用.在他的演講中,Peter Sommerlad 問了他一個我不太明白的問題.(鏈接直接指向問題,討論中的代碼直接發生在此之前)
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,這是否意味著我們現在實際上可以實現精簡版的概念?
Walter, would that mean we actually can implement concepts lite right now?
沃爾特回應了
哦耶!我已經完成了……它的語法不太一樣.
Oh yeah! I've done it ... It doesn't have quite the same syntax.
我理解這次交流是關于 Concepts Lite.這種模式真的那個通用嗎?無論出于何種原因,我都沒有看到它.有人可以解釋(或草圖)這樣的東西會是什么樣子嗎?這只是關于 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 來檢測類型語句是否格式正確,并使用它來實現 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&> {};
因為談話,我明白這個例子是如何工作的,但我不明白我們如何從這里得到像 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.此外,它還允許進行更深入的內省,以實現更好的重載.然而,這僅在概念謂詞被定義為 concept bool
時才有效.改進的重載不適用于當前的概念謂詞,但可以使用條件重載.讓我們看看如何在 C++14 中定義謂詞、約束模板和重載函數.這有點長,但它介紹了如何在 C++14 中創建完成此任務所需的所有工具.
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
的謂詞有點難看.相反,我們可以利用這樣一個事實,即我們可以使用尾隨 decltype 來約束函數(來自 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_
成員函數不可調用.所以我們可以創建一個 models
trait 來檢查 requires_
是否可以使用 void_t
調用:
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
{};
約束模板
所以當我們想基于概念約束模板時,我們仍然需要使用enable_if
,但我們可以使用這個宏來幫助使其更清晰:
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
所以我們可以定義一個基于Incrementable
概念約束的increment
函數:
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
的東西調用increment
,我們會得到這樣的錯誤:
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)>())>
^
重載函數
現在如果我們要做重載,我們要使用條件重載.假設我們要創建一個 std::advance
使用概念謂詞,我們可以這樣定義它(現在我們將忽略可遞減的情況):
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;
}
然而,當它與 std::vector
迭代器.我們想要做的是對調用進行排序,我們可以使用條件重載來完成.可以考慮寫這樣的東西(這不是有效的 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;
}
所以如果第一個函數沒有被調用,它將調用下一個函數.因此,讓我們從為兩個功能實現它開始.我們將創建一個名為 basic_conditional
的類,它接受兩個函數對象作為模板參數:
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)...);
}
};
所以現在這意味著我們需要將我們的函數定義為函數對象:
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 = {};
所以現在如果我們嘗試將它與 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
實際上有三個重載,所以我們可以使用 basic_conditional
來實現適用于任意數量的 conditional
使用遞歸的函數:
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
{};
所以,現在我們可以像這樣編寫完整的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 代替函數對象來編寫它,這有助于使其編寫起來更清晰.所以我們使用這個 STATIC_LAMBDA
宏來在編譯時構造 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() + []
并添加一個make_conditional
函數,即constexpr
:
template<class... Fs>
constexpr conditional<Fs...> make_conditional(Fs...)
{
return {};
}
那么我們現在可以像這樣編寫advance
函數:
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;
}
);
這比使用函數對象版本更緊湊和可讀.
Which is little more compact and readable than using the function object versions.
另外,我們可以定義一個modeled
函數來減少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;
}
);
最后,如果您有興趣使用現有的庫解決方案(而不是像我展示的那樣滾動自己的解決方案).Tick 庫提供了一個定義概念和約束模板的框架.而 Fit 庫可以處理函數和重載.
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.
這篇關于void_t“可以實現概念"?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!