英文:
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 anFtype functor takingArgstype 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 forhasFirsttrait) All I can find aboutvoidcasting 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::declvalis 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::forwardand 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论