Per CPP reference, std::is_function can be implemented as follows. Can someone explain why this works as it seemingly does not directly address callables?
template<class T>
struct is_function : std::integral_constant<
bool,
!std::is_const<const T>::value && !std::is_reference<T>::value
> {};
It exploits this sentence from https://eel.is/c++draft/basic.type.qualifier#1
A function or reference type is always cv-unqualified.
So, given a type T, it tries to make a const T. If the result is not a const-qualified type, then T must be a function or reference type. Then it eliminates reference types, and done.
(not to be confused with member functions that have const in the end: that is, in standardese, "a function type with a cv-qualifier-seq", not the same as a "cv-qualified function type")
Related
I'm currently trying to understand the std::execution proposal by studying libunifex's implementation. The library makes heavy use of template metaprogramming.
There's one piece of code I really have trouble understanding:
template <class Member, class Self>
Member Self::* _memptr(const Self&);
template <typename Self, typename Member>
using member_t = decltype(
(std::declval<Self&&>() .*
_memptr<Member>(std::declval<Self&&>())));
What exactly is member_t used for? It's used in the implementation of then:
template <typename Sender, typename Receiver>
requires std::same_as<std::remove_cvref_t<Sender>, type> &&
receiver<Receiver> &&
sender_to<member_t<Sender, Predecessor>, receiver_t<std::remove_cvref_t<Receiver>>>
friend auto tag_invoke(tag_t<connect>, Sender&& s, Receiver&& r)
noexcept(
std::is_nothrow_constructible_v<std::remove_cvref_t<Receiver>, Receiver> &&
std::is_nothrow_constructible_v<Function, member_t<Sender, Function>> &&
is_nothrow_connectable_v<member_t<Sender, Predecessor>, receiver_t<std::remove_cvref_t<Receiver>>>
// ----------------------^ here
)
-> connect_result_t<member_t<Sender, Predecessor>, receiver_t<std::remove_cvref_t<Receiver>>> { /* ... */ }
_memptr is a function declared in such a way that
_memptr<Member>(self)
will give you a pointer to member of <class type of self> of type Member regardless of self's constness or value category. So if Self is possibly a reference type,
Member Self::*
would be ill-formed, but decltype(_memptr<Member>(std::declval<Self&&>())) will still give you the type you want.
The result of applying std::declval<Self&&>() .* to the member pointer is that it will produce the referenced member with the value category one would expect from a member access expression via . when the left-hand side has the value category indicated by Self's reference-qualification. That means if Self is a lvalue reference the expression will also be a lvalue and if Self is a rvalue reference or a non-reference the result will be a xvalue (rvalue).
Applying decltype to the expression gives you correspondingly a lvalue or rvalue reference to the member's type. It tells you whether, given the value category of self as a forwarding reference with template parameter Self its member of type Member should be moved from or instead copied where necessary.
Now if you have e.g.
template<typename T>
void f(T&& t) {
auto s = (member_t<T, decltype(t.s)>)(t.s);
}
where s is a non-reference non-static data member of T, then s will be copy-constructed from t.s if f is passed a lvalue and move-constructed from it if passed a rvalue. Essentially it is std::forward for the member.
In your shown code it is not directly used this way, but instead the member's type with correct reference-qualification is passed to some type trait e.g. in std::is_nothrow_constructible_v<Function, member_t<Sender, Function>> to to check whether Function can be (nothrow) constructed from a Function lvalue or rvalue depending on whether or not s was passed a lvalue or rvalue.
It think this performs an equivalent action to what std::forward_like in C++23 will do when used as
template <typename Self, typename Member>
using member_t = decltype(std::forward_like<Self>(std::declval<Member>()));
(I would not call that template metaprogramming by the way. Nothing here is using template instantiations to implement some algorithm at compile-time.)
In an attempt to rewrite a predicate combinator like this
auto constexpr all = [](auto const&... predicates){
return [predicates...](auto const&... x){
return (predicates(x...) && ...);
};
};
(little generalization of this) in a way that it would give meaningful errors when fed with non-predicates/predicates with different arities/arguments, I started writing something like this:
template<typename T, typename = void>
struct IsPredicate : public std::false_type {};
template<typename T>
struct IsPredicate<T, std::enable_if_t<std::is_same_v<bool, return_type_of_callable_T>, void>>
: public std::true_type {};
and then I stared at it for a while... How do I even check what is the return type of a function, if I don't even know how to call it?
I see this:
I couldn't even pass decltype(overloaded_predicate_function) to IsPredicate, because template type deduction can't occur with an overloaded name,
even if I only talk of function objects, the problem of the first bullet point could apply to operator(), in case it is overloaded.
So my question is: is it even possible to determine the return type of an arbitrary callable?
I'm mostly interested in a C++17 answer, but, why not?, I'd also like to know what C++20's concept offer in this respect.
So my question is: is it even possible to determine the return type of an arbitrary callable?
No. You can only do this in very narrow circumstances:
the callable is a pointer to member data / pointer to member function
the callable is a pointer/reference to function
the callable is a function object with a single non-overloaded function call operator that is not a template and no conversion functions to function pointers/reference
That's it. If you have a function object whose call operator is either overloaded or a template, you can't really figure out what its return type is. Its return type could depend on its parameter type, and you may not have a way of knowing what the parameter types could be. Maybe it's a call operator template that only accepts a few specific types that you have no way of knowing about, but it is a predicate for those types?
The best you can do is defer checking until you know what what arguments are. And then C++20 already has the concept for you (predicate):
inline constexpr auto all = []<typename... Ps>(Ps const&... predicates){
return [=]<typename... Xs>(Xs const&... x)
requires (std::predicate<Ps const&, Xs const&...> && ...)
{
return (std::invoke(predicates, x...) && ...);
};
};
Note that you should use std::invoke to allow for pointers to members as predicates as well (and this is what std::predicate checks for).
You cannot determine the return type of a callable without specifying the argument types (usually done by providing actual arguments) because the return type could depend on the argument types. "decltype(potential_predicate)" won't work but "decltype(potential_predicate(args...))" is another matter.
The first will be the type of the callable itself (whether pointer-to-function or a class type or whatever) while the second will produce the return type of the callable expression.
Can't give you a C++17 answer, but since you also asked for concepts:
The requires expression states that the () operator is overloaded and returns a bool. I think a predicate in the classical sense takes two arguments, but the concept can be easily extended to fo fulfill that requirement as well.
template<typename T>
concept IsPredicate =
requires(T a) {
{ a() } -> std::same_as<bool>;
};
I want a function like this:
template<typename C, typename T>
void foo(C &&aclass, T (C::*const memberFunc)(unsigned)) {
}
The parameters are (in words because C/C++ type syntax is mental):
A universal reference to a class, e.g. MyClass.
A const pointer to a member function of MyClass that takes an unsigned int and returns T.
This sort of works, however if I call it with an l-value reference as the first parameter I get an error like:
candidate template ignored: deduced conflicting types for parameter 'C' ('MyClass &' vs. 'MyClass')
As far as I understand it, it is deducing C from the first and second parameters, but comes up with different deductions and gets confused.
According to this answer you can make it only do deduction on the first parameter, and somehow use the typename keyword on the second parameter. But I can't work out the syntax to do this when I do want it to deduce one of the types in the parameter (T), but not the other (C).
This answer is also helpful but they solve it by just not using references for C at all, which in that case is equally efficient, but not in mine.
Is this possible?
With lvalues C will be deduced to be an lvalue-reference type (i.e. MyClass & for your case) for the 1st parameter, which is the expected behavior of forwarding reference; you can remove the reference-ness via std::remove_reference when using C in the 2nd parameter, e.g.
template<typename C, typename T>
void foo(C &&aclass, T (std::remove_reference_t<C>::*const memberFunc)(unsigned)) {
}
And as #Quentin pointed, using of std::remove_reference also introduces non-deduced context, that would prevent C from being deduced from the 2nd parameter.
Actually I just found that having a universal reference overload forward it to an l-value reference version works. Not particularly elegant though; I feel like there should be a better way.
template<typename C, typename T>
void foo(C &aclass, T (C::*const memberFunc)(unsigned)) {
// Code goes here.
}
template<typename C, typename T>
void foo(C &&aclass, T (C::*const memberFunc)(unsigned)) {
foo(aclass, memberFunc);
}
I know that sizeof operator doesn't evaluate its expression argument to get the answer. But it is not one of the non-deducted contexts for templates. So I am wondering how it interacts with templates and specifically template argument deductions. For instance, the following is taken from C++ Templates: The Complete Guide:
template<typename T>
class IsClassT {
private:
typedef char One;
typedef struct { char a[2]; } Two;
template<typename C> static One test(int C::*);
template<typename C> static Two test(...);
public:
enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
enum { No = !Yes };
};
This type function determines, as its name suggests, whether a template argument is a class type. The mechanism is essentially the following condition test:
sizeof(IsClassT<T>::test<T>(0)) == 1
Note, however, the function template argument is explicit (T in this case) and the function argument is a plan 0 of type int, which is not of type pointer to an int member of class C. In normal function template argument deduction, when T is really of class type and function argument is simply a 0, deduction on static One test(int C::*); should fail since implicit conversion (0 used as null pointer type) is not allowed during template argument deduction and (I guess?) SFINAE should kick in and overload resolution would have selected
static Two test(...);
However, since the whole expression is wrapped inside the sizeof operator, it seems that passing the 0 without a cast works.
Can someone clarify:
if my understanding of function template argument deduction is correct?
if it is because of the non-evaluation nature of sizeof operator that makes passing 0 successful? And
if 0 doesn't matter in this context, we could choose any argument in place of 0, such as 0.0, 100 or even user defined types?
Conclusion: I found in C++ Primer that has a section on function template explicit arguments. And I quote "Normal Conversions Apply for Explicitly Specified Arguments" and "For the same reasons that normal conversions are permitted for parameters that
are defined using ordinary types (ยง 16.2.1, p. 680), normal conversions also apply
for arguments whose template type parameter is explicitly specified". So the 0 in this question is actually implicitly converted to null pointer to members (pointer conversion).
Template Argument Deduction is done when instantiating a function. This is done as part of function overloading (and other contexts not applicable here). In TAD, the types of function arguments are used to deduce the template parameters, but not all arguments are necessarily used. This is where the "non-deduced context" comes from. If a template parameter appears in a non-deduced context within a function signature, it can't be deduced from the actual argument.
sizeof(T) is in fact a non-deduced context for T, but it's so obvious that nobody even bothered to mention it. E.g.
template< int N> class A {};
template<typename T> void f(A<sizeof(T)>);
f(A<4>());
The compiler isn't going to pick a random T that has sizeof(T)==4.
Now your example actually doesn't have a sizeof inside the argument list of a function template, so "non-deduced context" is an irrelevant consideration. That said, it's important to understand what "sizeof doesn't evaluate its expression argument" means. It means the expression value isn't calculated, but the expression type is. In your example, IsClassT<T>::test<T>(0) won't be called at runtime, but its type is determined at compile time.
I saw possible implementations for std::remove_reference as below
template< class T > struct remove_reference {typedef T type;};
template< class T > struct remove_reference<T&> {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};
Why is it that there are specializations for lvalue and rvalue reference? Won't the general template itself be sufficient and remove the reference? I'm confused here because in the T& or T&& specialization if I try to use ::type I should still get T& or T&& respectively right?
Could you explain how, why we cast to remove_reference<t>::type&& in move? (is it because that the parameter is named so it will be treated as an lvalue inside the move function?).
Also, could you point out a way whereby I can find out and print what the type is? for e.g if its an rvalue of type int then I should be able to print out that int&& was passed? (I've been using std::is_same to check but manually.)
Thank you for your time.
why is it that there are specializations for lvalue and rvalue reference?
If only the primary template existed, then doing:
remove_reference<int&>::type
Would give you:
int&
And doing:
remove_reference<int&&>::type
Would give you:
int&&
Which is not what you want. The specializations for lvalue references and rvalue references allow stripping the & and the &&, respectively, from the type argument you pass.
For instance, if you are doing:
remove_reference<int&&>
The type int&& will match the pattern specified by the T&& specialization, with T being int. Since the specialization defines the type alias type to be T (in this case, int), doing:
remove_reference<int&&>::type
Will give you int.
could you explain how, why we cast to remove_reference<t>::type&& in move?
That's because if move() were defined as follows:
template<typename T>
T&& move(T&& t) { ... }
// ^^^
// Resolves to X& if T is X& (which is the case if the input has type X
// and is an lvalue)
Then the return type will be X& if the argument of move() is an lvalue of type X (that's how so-called "universal references"). We want to make sure that the return type is always an rvalue reference.
The purpose of move() is to give you back an rvalue, no matter what you pass in input. Since a function call for a function whose return type is an rvalue reference is an rvalue, we really want move() to always return an rvalue reference.
That's why we do remove_reference<T>::type&&, because appending && to a non-reference type is always guaranteed to yield an rvalue reference type.
Also could you point out a way whereby I can find out and print what the type is?
I'm not sure what you mean by "print" here. There is no portable way I know of converting the name of a type to a string (no matter how you obtain that type).
If your goal is to make sure that an rvalue was passed, on the other hand, you could use a static assertion like so:
#include <type_traits>
template<typename T>
void foo(T&&)
{
static_assert(!std::is_reference<T>::value, "Error: lvalue was passed!");
// ...
}
Which relies on the fact that when an lvalue of type X is being passed, T will be deduced to be X&.
You could also use an equivalent SFINAE-constraint, if you only want to produce a substitution failure:
#include <type_traits>
template<typename T, typename std::enable_if<
!std::is_reference<T>::value>::type* = nullptr>
void foo(T&&)
{
// ...
}
When you treat some type as template parameter, compiler searches for the most "specialized" specialization. If you pass a int&& to this template, compiler use remove_reference<T&&> version. General specialization don't give you what you want - if you pass int&& to general specialiation, type will be int&&
If you want to print type, use typeid(some_type).name()