英文:
Have difficulty understanding the syntax of generic lambdas for SFINAE-based traits
问题
-
对于第一个问题,
std::declval<F>()(std::declval<Args&&>()...)
表示一个类型为F
的可调用对象(functor),它接受类型为Args
的参数。为什么要使用 forwarding reference (Args&&
) 而不是简单的Args
?这是因为这里使用的是 SFINAE(Substitution Failure Is Not An Error)技术,它允许在编译时根据模板参数的类型进行条件编译。使用 forwarding reference 可以确保参数传递方式被保留,从而使编译器可以正确地匹配和推断参数类型。 -
对于第二个问题,
decltype(isValidImpl<decltype(f), decltype(args)&&...>(nullptr))
也是使用了 SFINAE 技术。这里之所以传递decltype(args)&&
而不是decltype(args)
,是因为 forwarding reference 保留了参数的引用性质,这对于正确匹配和推断参数类型非常重要。编写通用的 SFINAE 代码时,通常需要保留参数的引用性质以便进行正确的类型检查。 -
对于第四个问题,
decltype((void)decltype(valueT(x))())
中的(void)
强制类型转换的目的是消除表达式的返回值,使其成为一个表达式语句,同时抑制编译器对于返回值的不必要警告。这种技巧常常用于 SFINAE 上下文中,因为 SFINAE 依赖于函数重载和函数模板匹配,而不是函数的返回值。(void)
强制类型转换确保了编译器不会把这个表达式的返回值视为重要,而只关注函数签名是否有效。这是一种常见的做法,用于确保 SFINAE 行为的正确性。
希望这些解释能够帮助你更好地理解这些复杂的语法和技巧。如果你还有其他问题,请随时提出。
英文:
I am reading some examples of SFINAE-based traits, but unable to make sense out of the one related to generic lambdas in C++17 (isvalid.hpp).
I can understand that it roughly contains some major parts in order to implement a type trait such as isDefaultConstructible
or hasFirst
trait (isvalid1.cpp):
1. Helper functions using SFINAE technique:
#include <type_traits>
// helper: checking validity of f(args...) for F f and Args... args:
template<typename F, typename... Args,
typename = decltype(std::declval<F>()(std::declval<Args&&>()...))>
std::true_type isValidImpl(void*);
// fallback if helper SFINAE'd out:
template<typename F, typename... Args>
std::false_type isValidImpl(...);
2. Generic lambda to determine the validity:
// define a lambda that takes a lambda f and returns whether calling f with args is valid
inline constexpr
auto isValid = [](auto f) {
return [](auto&&... args) {
return decltype(isValidImpl<decltype(f),
decltype(args)&&...
>(nullptr)){};
};
};
3. Type helper template:
// helper template to represent a type as a value
template<typename T>
struct TypeT {
using Type = T;
};
// helper to wrap a type as a value
template<typename T>
constexpr auto type = TypeT<T>{};
// helper to unwrap a wrapped type in unevaluated contexts
template<typename T>
T valueT(TypeT<T>); // no definition needed
4. Finally, compose them into isDefaultConstructible
trait to check whether a type is default constructible:
constexpr auto isDefaultConstructible
= isValid([](auto x) -> decltype((void)decltype(valueT(x))()) {
});
It is used like this (Live Demo):
struct S {
S() = delete;
};
int main() {
std::cout << std::boolalpha;
std::cout << "int: " << isDefaultConstructible(type<int>) << std::endl; // true
std::cout << "int&: " << isDefaultConstructible(type<int&>) << std::endl; // false
std::cout << "S: " << isDefaultConstructible(type<S>) << std::endl; // false
return 0;
}
However, some of the syntax are so complicated and I cannot figure out.
My questions are:
-
With respect to 1, as for
std::declval<F>()(std::declval<Args&&>()...)
, does it mean that it is anF
type functor takingArgs
type constructor? And why it uses forwarding referenceArgs&&
instead of simplyArgs
? -
With respect to 2, as for
decltype(isValidImpl<decltype(f), decltype(args)&&...>(nullptr)){}
, I also cannot understand why it passes forwarding referencedecltype(args)&&
instead of simplydecltype(args)
? -
With respect to 4, as for
decltype((void)decltype(valueT(x))())
, what is the purpose of(void)
casting here? ((void)
casting can also be found in isvalid1.cpp forhasFirst
trait) All I can find aboutvoid
casting is https://stackoverflow.com/questions/69314599/casting-to-void-to-avoid-use-of-overloaded-user-defined-comma-operator, but it seems it is not the case here.
Thanks for any insights.
P.S. For one who wants more detail could check C++ Templates: The Complete Guide, 2nd - 19.4.3 Using Generic Lambdas for SFINAE. The author also mentioned that some of the techniques are used widely in Boost.Hana, so I also listen to Louis Dionne's talk about it. Yet, it only helps me a little to understand the code snippet above. (It is still a great talk about the evolution of C++ metaprogramming)
答案1
得分: 1
-
F 是一个可调用的函数对象,带有参数 Args...
为了理解起来更清晰,将std::declval<F>()
想象成一个 "类型为 F 的完全构造的对象"。std::declval
只是为了防止 F 不可默认构造并仍需在未评估的上下文中使用。
对于可默认构造的类型,等效的写法是:
F()(std::declval<Args&&>()...);
本质上是对 F 的构造函数的调用,然后使用转发的Args
调用其operator()
。但想象一种类型可用 int 构造,另一种类型可默认构造,还有一种类型需要一个字符串。如果没有一些未评估的构造函数类似的元函数,将无法覆盖所有这些情况。
您可以在 Alexandrescu 的 Modern C++ Design: Generic Programming and Design Patterns Applied 中阅读更多相关内容。 -
向参数类型添加
&&
等效于完美转发它。这可能看起来有些晦涩,但它只是decltype(std::forward<decltype(args)>(args))
的一种缩写。更多细节,请参阅std::forward
的实现 和 引用折叠规则。请注意,此片段添加的是右值引用,当与原始类型组合时会折叠为正确的引用,而不是转发引用。 -
正如评论中所述:实际上不需要该类型,有可能无法返回它,它的存在只是为了检查表达式的正确性,之后可以丢弃它。
英文:
-
F is a function object callable with Args...
For the sake of mental model, picturestd::declval<F>()
as a "fully constructed object of type F".std::declval
is there just in case F is not default-constructible and still needs to be used in unevaluated contexts.
For a default-constructible type this would be equivalent:
F()(std::declval<Args&&>()...);
In essence it's a call to F's constructor and then call to itsoperator()
with forwardedArgs
. But imagine one type is constructible with int, another one is default-constructible, yet another one requires a string. Without some unevaluated constructor-like metafunction it would be impossible to cover all those cases.
You can read more on that in Alexandrescu's Modern C++ Design: Generic Programming and Design Patterns Applied. -
Adding
&&
to the argument type is effectively perfect-forwarding it. It may look obscure, but it's just a shorthand fordecltype(std::forward<decltype(args)>(args))
. See the implementation ofstd::forward
and reference collapsing rules for more details. Keep in mind though, that this snippet adds rvalue-reference that collapses to the correct one when combined with the original type, not a forwarding one. -
As it was stated in the comments: the type is not really needed, possibilty exists it cannot be returned, its presence there is just to check expression's correctness, afterwards it can be discarded.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论