Why "Foo F(Bar());" Can Be a Declaration of a Function That Takes Type Bar and Returns Type Foo

Why Foo f(Bar()); can be a declaration of a function that takes type Bar and returns type Foo?

The function f actually takes a function pointer to a function that takes no arguments and gives a Bar. The type of the argument to f is Bar (*)().

This code fails to compile (and we can see the actual type of the argument in the error message):

class Foo { };
class Bar { };

Foo f(Bar());

int main() {
Bar b;
f(b);
return 0;
}

But this code does compile:

class Foo { };
class Bar { };

Foo f(Bar());

Bar g();

int main() {
f(g);
return 0;
}

The second meaning it could have, as you say in the question, is that you are making a new Foo object called f and you are calling the constructor with Bar() (a new instance of Bar). It would be similar to:

Foo f = Foo(Bar());

In this situation of Foo f(Bar()); though, the first interpretation is chosen by the compiler.

Somewhat confusingly, if you add another set of parentheses, as in

Foo f((Bar()));

the compiler picks the second interpretation.

Syntax of an un-named function pointer in C++

Fully explicit form:

Foo bar(Baz f());

bar is a function that takes a single parameter f, which is a function (taking no arguments) returning Baz.

Without naming the parameter:

Foo bar(Baz ());

The reason bar ends up taking a pointer to a function is that functions cannot be passed by value, so declaring a parameter as a function automatically decays it into a pointer. The above declaration is equivalent to:

Foo bar(Baz (*)());

// or:
Foo bar(Baz (*f)()); // with a named parameter

This is similar to void foo(int [10]) where int [10] also means int * in a parameter list.

How is this a most vexing parse?

Rectangle s(origin()); is a vexing parse too. It declares a function s which returns rectangle, and takes as argument pointer to function returning origin. Not sure what you meant by "works fine".

rectangle w( origin(), extents() ); declares a function w returning rectangle and taking arguments: pointer to function returning origin, and pointer to function returning extents.

For more detail see this question or browse the other questions under the most-vexing-parse tag.

What is this member pointer syntax?

struct Trait<T U::*>
^^^^^^ // I don't understand this syntax

The above syntax means that we've a pointer to a member of a class named U where the member has type T. In other words, a pointer to a member of class U that has type T.

Now let's apply this to &Foo::bar. The type of the expression &Foo::bar is:

void (Foo::* f)()

Now you can compare this with T U::*

So, after comparing we get:

  1. U = Foo which is the class type.
  2. T = void() which means a function that has the return type void and takes no parameter i.e., a function type.

which effectively means that we've a pointer to a member function that has the return type void and takes no parameters, of class Foo.

Keep type hints for a TypeScript function that can take any arguments

Your Foo type accepts any string-returning function. But when you assign a value to a variable of type Foo, the compiler does not track the particular value you've assigned. In some sense the compiler treats alpha, bravo, and charlie, as opaque boxes labeled Foo, and that's all it knows about the contents. So while it's really easy to provide a value of type Foo, it's almost impossible to consume one.

This forgetfulness is the general behavior for variables of non-union types. For union types, there is assignment narrowing where the appearent type of a variable will be narrowed based on the inferred type of the assigned value. That lets you write const x: string | undefined = "hello"; x.toUpperCase(); without error, since x is narrowed from string | undefined to string by the assignment. It would be nice if this worked in some way for non-unions, and there is a longstanding request at microsoft/TypeScript#16976 to do this, but I don't see any indication this will ever be implemented (it would be a big breaking change to do so). You can't plausibly represent Foo as a union type, and unions of functions have other complicating behavior anyway, so this is not a feasible solution for you.

Instead, my suggestion is that you really want something like the so-called satisfies operator, as discussed in microsoft/TypeScript#47920 and originally microsoft/TypeScript#7481. You want alpha and bravo and charlie to satisfy the Foo type without being widened to the Foo type. There's no built-in operator that works this way, but you can implement a generic identity helper function that gives you this behavior (this workaround is discussed in those GitHub issues):

type Foo = (...args: never) => string
const asFoo = <F extends Foo>(f: F) => f;

Instead of annotating variables like const x: Foo = ..., you use the helper function like const x = asFoo(...). Here's how it works:

const alpha = asFoo(() => `no arguments`);
console.log(alpha()) // okay

const bravo = asFoo((a: string) => `uses one argument${a}`);
bravo() // error, argument of type sting expected

const charlie = asFoo((a: number, b: string) => `uses two arguments: ${a} and ${b}`);
charlie() // error, two arguments expected

Looks good!

Note that if you actually want to use Foo objects programmatically you will need to keep track of the arguments list types by using generics, like this:

type GenericFoo<A extends any[]> = (...args: A) => string;
function useFoo<A extends any[]>(foo: GenericFoo<A>, ...args: A): string {
return foo(...args);
}
useFoo(alpha); // okay
useFoo(bravo, "a"); // okay
useFoo(bravo, 123); // error, number is not string
useFoo(bravo); // error, expect another argument

Playground link to code

Is there a technical reason for not allowing a using declaration to make a function name accessible under a different name?

See N1489:

It is possible to generalize the notion of alias beyond types and
namespaces to functions, variables, etc. We do not see sufficient
benefits from doing this and can imagine serious overuse leading to
confusion about which functions and variables are used. Consequently,
we do not propose the generalizations mentioned in this section.
Furthermore, we do not plan to work further on these generalizations
unless someone comes up with examples that indicate significant
usefulness.



Related Topics



Leave a reply



Submit