is_function<T>
returns true
if type T
is of type “function”, and false
if not. So, we need to write a meta-function without using multiple specializations for a different number of argument types for the function type being examined. You can look at an example implementation with specializations in boost . At the moment there are 25 maximum arguments and it all takes up quite a lot of space. some_template<A1, A2, A3 /*, etc. */>
as a single “package” of parameters, parameter pack. Parameter pack helps to make generalized specializations, overloads and substitutions in cases where the number of arguments is unknown. It is through this tool in C ++ 11 that is_function is implemented. In C ++ 98/03 there was no such tool. This means that, in general, if we needed to provide a different number of arguments depending on the situation, we had to make overloads and specialization “for all occasions”. If you look at the implementations of such libraries as variant or mpl in boost, then make sure that such code is abundant (sometimes it is generated by the preprocessor). If we needed to determine whether type T
function of R(A1, A2)
, then the simplest and most obvious solution was to create an appropriate specialization: template <typename F> struct is_function { /* .... */ }; template <typename R, typename A1, typename A2> struct is_function<R(A1, A2)> { /* .... */ };
Often, it was simply impossible to do otherwise. I don’t want you to think that I’m dissatisfied with the implementation in boost - their solution is the most portable, therefore, the most correct, especially in the context of such a library. But I was interested in the task to do without it.An example of the type of a pointer to a pointer to a function.So, a function can be implicitly converted to a function pointer. Arrays have the same property: the array is implicitly converted to a pointer. Let's see what else there is:
If you have an “array of T” or “function returning”, you’re adjusted for toThis means that the “function” type, when specified as a parameter of another function, implicitly acquires the properties of a pointer. Based on this, in paragraph 13.1 , it is described that such a declaration
void foo(int ());
corresponds to this (i.e., it is the same): void foo(int (*)());
This is what we can already use. We can write a check based on this and define a function in front of us or not. But everything is not as simple as it seems, but more on that later. In the meantime, let's see what else you can use, based on the type of "function":In the declaration TD where D has the formYeah, that's interesting too. Those. we can not get an array of elements of type "function". Along with this, we cannot get an array of references, a
D1 [constant-expression opt ]
D1 is the derived-declarator-type-list-T, ”then it is the type of identifier of the identifier of the type. T is called the array element type; This is a void, a function type or an abstract class.
void
array, and an array with elements of the abstract class type. If we write a check that cuts out the remaining options, then we will be able to accurately determine the function in front of us or not.F
being checked is a function if equality holdsvoid( F ) == void( F * )
. template <typename F> static void (* declfunc() )( F ); template <typename F> static void (* gen( void (F *) ) )( F ); template <typename F> static void (* gen( void (F ) ) )( F * );
Looking ahead, I will say that this code comes from erroneous assumptions. But Clang compilers (up to version 3.4 inclusive), GCC compilers (up to version 4.9), compilers from VS (cl 19.x, maybe earlier ones) compiled it as I expected. I'll tell you how it works and how I planned to use it. First, let's write the function declaration that will help us with the checks: template <typename X> static char (& check_is_function( X ) ) [ is_same<void(*)( F ), X>::value + 1 ];
If the type passed to check_is_function coincides with void(*)( F )
, then the function returns a reference to an array of two char
, if it does not match - from one char
(the type of the return value we can then analyze using sizeof
). Everywhere here we assume that F
is a type that we examine for belonging to the type “function”. Now, if you put this into a simple template, template <typename F> struct is_function { template <typename X> static char (& check_is_function( X ) ) [ is_same<void(*)( F ), X>::value + 1 ]; enum { value = sizeof( check_is_function( gen( declfunc<F>() ) ) ) - 1 }; };
we can make sure that the compilers mentioned above to express the form is_function<int()>::value; is_function<int>::value; typedef void fcv() const; is_function<fcv>::value;
we will get the results of 1, 0 and 1, respectively (the full code can be found here , and run here ). Yes, this is not a fully working solution, here we do not distinguish function pointers from functions, there is a problem with references, void
, etc. But it all just costs and emphasize, I would not like that. If we run the same example on the newer compilers (GCC> = 4.9, Clang> = 3.5, cl 19.x), then we will make sure that the output has changed. Now we get the results 1, 0, 0, respectively. This happens because the type of the function with cv-qualifier-seq (this is the same const
or volatile
at the end), which is substituted into the type of another function (acquiring the properties of a pointer), has ceased to be a valid argument substitution in the variant: template <typename F> static void (* gen( void (F *) ) )( F );
Why? Because with the introduction of a new standard in which it is clearly written (the last draft),Forming a function type cv-qualifiers or a ref-qualifier;the compiler's approach to this code has changed. Adding an asterisk to this type is a wrong substitution, so the code stopped working. In C ++ 03 there was no equally clear rule (you can read about the changes here ). Which, of course, does not mean that it should have been allowed there. However, the nebula of standard language left the opportunity to skip this point, which is written on the following link:
It is not clear that it has been established during the submission.Therefore, many modern compilers still do not take this into account (for example, cl 18.x or icc 13.x and 14.x). The attentive reader probably already wondered that if the explicit addition of an asterisk to the type of “function with cv-qualifier-seq ” is not allowed, then an implicit one, if you specify this type as a parameter, should not be available either. Yes, it probably is. However, at the moment, there is not a single compiler that explicitly prohibits this.
It is a function of a function of determination of a function.This tells us about a rather narrow context of using function types with cv-qualifier-seq . And our case does not seem to fit there.
template <typename F> struct may_add_ptr { template <typename P> static char (& may_add_ptr_check(P *) )[2]; template <typename P> static char (& may_add_ptr_check(...) )[1]; enum { value = sizeof( may_add_ptr_check<F>(0) ) - 1 }; };
If the P*
substitution is incorrect, then an overload with an ellipsis is selected. Depending on the selected sizeof
overload from the return value, returns either 1 or 2. By subtracting the unit we will get the value value
0 or 1, where 1 is obtained if it is possible to substitute a pointer to the type, and 0 if not (then I will use this reception in the same way). Now we have the opportunity to form the type of function pointer based on this check. We can do it in different ways - based on overload or specialization. I will show the method based on overload, because he is more portable. template <typename F> static typename enable_if< may_add_ptr<F>::value == 1, void (*)(typename remove_reference<F>::type *) >::type declfunc(); template <typename F> static typename enable_if< may_add_ptr<F>::value == 0, void (*)(typename remove_reference<F>::type ) >::type declfunc();
So, we have formed the type type
- a pointer to a function whose parameter is another type. This is the type that we are investigating for belonging to the type “function”. So ifdeclfunc<F>() == void(*)( F )
F
is a function. The removal of the link ( remove_reference
) is necessary, in this case we will automatically get the inequality in caseF = R(&)(Args)
, or F = T &
void(*)( R(*)(Args) )
and void(*)( R(&)(Args) )
void(*)( T )
and void(*)( T & )
F
is a function of the form R(Args)
, then they will be comparedvoid(*)( R(*)(Args) )
and void(*)( R(Args) )
.F
is a function of the form R(Args) const
, then they will be comparedvoid(*)( R(Args) const )
and void(*)( R(Args) const )
.F = T
(not a function), then they will be comparedvoid(*)( T * )
and void(*)( T )
.is_same
based is_same
, since The argument of our is_function
can also be an abstract type, the use of which in this context will result in a compilation error. Therefore, we will replace the is_same
with an SFINAE check of the following sense: template <typename F> static char (& is_function_check( void( F ) ) )[2]; template <typename F> static char (& is_function_check( ... ) )[1];
We use it like this: value = sizeof( is_function_check<Tp>( declfunc<Tp>() ) ) - 1;
template <typename Tp> struct is_function { private: template <typename F> struct may_add_ptr { template <typename X> static char (& may_add_ptr_check(X *) )[2]; template <typename X> static char (& may_add_ptr_check(...) )[1]; enum { value = sizeof( may_add_ptr_check<F>(0) ) - 1 }; }; template <typename F> static typename enable_if< may_add_ptr<F>::value == 1, void (*)(typename remove_reference<F>::type *) >::type declfunc(); template <typename F> static typename enable_if< may_add_ptr<F>::value == 0, void (*)(typename remove_reference<F>::type ) >::type declfunc(); template <typename F> static char (& is_function_check( void( F ) ) )[2]; template <typename F> static char (& is_function_check( ... ) )[1]; public: enum { value = sizeof( is_function_check<Tp>( declfunc<Tp>() ) ) - 1 }; };
#define TEST_IS_FUNCTION(Type, R) \ std::cout << ((::is_function<Type>::value == R) ? "[SUCCESS]" : "[FAILED]") \ << " Test is_function<" #Type "> (should be [" #R "]):" \ << std::boolalpha \ << (bool)::is_function<Type>::value << std::endl
And run on the following struct S { virtual void f() = 0; }; int main() { typedef void f1() const; typedef void f2() volatile; typedef void f3() const volatile; TEST_IS_FUNCTION(void(int), true); TEST_IS_FUNCTION(void(), true); TEST_IS_FUNCTION(f1, true); TEST_IS_FUNCTION(void(*)(int), false); TEST_IS_FUNCTION(void(&)(int), false); TEST_IS_FUNCTION(f2, true); TEST_IS_FUNCTION(f3, true); TEST_IS_FUNCTION(void(S::*)(), false); TEST_IS_FUNCTION(void(S::*)() const, false); TEST_IS_FUNCTION(S, false); TEST_IS_FUNCTION(int, false); TEST_IS_FUNCTION(int *, false); TEST_IS_FUNCTION(int [], false); TEST_IS_FUNCTION(int [2], false); TEST_IS_FUNCTION(int **, false); TEST_IS_FUNCTION(double, false); TEST_IS_FUNCTION(int *[], false); TEST_IS_FUNCTION(int &, false); TEST_IS_FUNCTION(int const &, false); TEST_IS_FUNCTION(void(...), true); TEST_IS_FUNCTION(int S::*, false); TEST_IS_FUNCTION(void, false); TEST_IS_FUNCTION(void const, false); }
F
with cv-qualifier-seq to be a valid substitution (for example, icc 13.x). On these compilers, verification will not work.void
template <typename Tp> struct is_class;
Now we need to remove from consideration references and void. Use the following two templates for this: template <typename Tp> struct is_lvalue_reference; template <typename Tp> struct is_void;
It seems to be all, but something is missing. In fact, there is another type that cannot be an element of an array — it is an array of unknown bound (arrays of unknown size T[]
). We also need to weed out. In principle, you can not suffer and sift out all the arrays at once. template <typename Tp> struct is_array;
Implementation of these metafunctions can be found here , or take, for example, from boost. template <typename Tp> struct is_function { private: template <typename F> static char (& check_is_function( ... ) )[2]; template <typename F> static char (& check_is_function( F (*)[1] ) )[1]; public: enum { value = !is_class<Tp>::value && !is_void<Tp>::value && !is_lvalue_reference<Tp>::value && !is_array<Tp>::value && (sizeof( check_is_function<Tp>(0) ) - 1) }; };
Checking whether a type can be an element of an array is organized through the definition of the type “pointer to an array”, with elements of the type being tested as a parameter of the function check_is_function
. If the substitution is unsuccessful, then type F
is a function.A typedef of a function type declarant includes a cv-qualifier-seq shall be usedThose. You can use the typedef function type with cv-qualifier-seq :
only to declare a function, to declare a function
pointer to member refers to a function typedef function.
[Example:—End example]
typedef int FIC(int) const; FIC f; // ill-formed: does not declare a member function struct S { FIC f; // OK }; FIC S::*pm = &S::f; // OK
A type qualifier-seq or a ref-qualifier (7.1.3, 14.1) shall appear only as:Agree, it is much more understandable (note 6.3, now it legalizes the use of cv-qualifier-seq in the
(6.1) - the function type for a non-static member function,
(6.2) - this function refers to
(6.3) - the function typedef declaration or alias-declaration,
(6.4) - the type-id of the type-parameter (14.1), or
(6.5) - the type-id of a template-argument for a type-parameter (14.3.1).
[Example:- end example]
typedef int FIC(int) const; FIC f; // ill-formed: does not declare a member function struct S { FIC f; // OK }; FIC S::*pm = &S::f; // OK
typedef
type declaration of the top-level function and only , no another function). The fact that such a type can now be used as a parameter of a function is the subject of bug reports that I made. In addition, in this case, these types will be promoted to the pointer, and this is also prohibited ( 8.3.1 / 4 ).Source: https://habr.com/ru/post/277727/
All Articles