I came across the following code:
char (&f(...))[2];
I checked the type of it (using typeid and c++filt) and got:
char (&(...)) [2]
But I can't make sense of this type. The [2] is the part that throws me off. Without it, I can replicate the type in a function definition, for example:
char (&f(...))
f is of the same type as h in (at least from the output of typeid + c++filt):
char& h(...)
It is a function declaration that has varying number of parameters and returns a reference to a character array of two elements.
It could be declared simpler by using a typedef. For example
typedef char char_array[2];
char_array & f( ... );
You can use cdecl and substitute an arbitrary type for the ellipse, such as int
:
char (&f(int)) [2]
Leads to
declare
f
as function (int
) returning reference to array 2 ofchar
[…]
Substitute back and you have your declaration in words.
char (&f(...))[2];
This is the declaration of a function returning a reference to an array of two characters. The parenthesis are needed for this to be syntactically correct. Otherwise the &
will bind to char
and there will be a syntax error because of [2]
being meaningless.
The syntax can be decomposed with a type alias. For example:
using array_ref = char (&)[2];
array_ref f(...);
The reason for returning a reference to an array rather than an actual array is because of the fact that arrays cannot be returned from functions. It's impossible. You can only return references or pointers to arrays, just like functions.
In all examples, ...
is a C variadic argument pack.
The only places where I've seen this kind of syntax is where it's being used as part of function overload resolution for SFINAE. Usually this function accompanies an overload of the same name that uses template substitution to check an attribute of a given type. If a substitution failure occurs the second overload (the one that takes a variadic pack) is chosen as the fallback. Its return type is what differentiates success or failure.
For example, here's trait class that checks if a type has a member function f()
:
template <typename T>
struct has_f
{
private:
using true_type = char (&)[1];
using false_type = char (&)[2];
template <typename U>
static decltype(std::declval<U>().f(), true_type()) f(int);
template <typename>
static false_type f(...);
public:
static constexpr bool value = sizeof(check<T>(0)) == 1;
};
As you can see, if T
has a member function f()
, then the size of the type returned will be 1
, otherwise 2
. true_type
and false_type
are largely superseded by the standard traits classes std::true_type
and std::false_type
these days, but this is simply an example to illustrate its use.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With