Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does the type "char (&(...)) [2]" mean?

Tags:

c++

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(...)

like image 898
dcmm88 Avatar asked Oct 17 '14 19:10

dcmm88


3 Answers

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( ... );
like image 183
Vlad from Moscow Avatar answered Oct 25 '22 08:10

Vlad from Moscow


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 of char […]

Substitute back and you have your declaration in words.

like image 29
Columbo Avatar answered Oct 25 '22 07:10

Columbo


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.

like image 23
David G Avatar answered Oct 25 '22 08:10

David G