0

Consider the following code:

#include <iostream>

using f = void(std::string);

void fcorrect(f func, std::string s) {
    func(s); // calling function
};

void fmistake(f func, std::string s) {
    f(s); // calling type alias instead of function
};

void myprint(std::string s) {
    std::cout << s << std::endl;
};

int main() {
    std::string s = "message";
    fcorrect(myprint, s);
    fmistake(myprint, s);
    return 0;
}

Function fcorrect receives myprint function and a string as arguments and prints this string as expected. Function fmistake contains a deliberate mistake - it calls type alias f instead of function func. It prints nothing.

I wonder what's happening under the hood when the latter function call takes place? Namely, why no compile time or runtime error are thrown?

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
vtm11
  • 165
  • 1
  • 9
  • 6
    `f(s)` is a declaration, equivalent to `f s;` (redundant parentheses are allowed). Which in turn is equivalent to `void s(std::string);`, a forward declaration of a function named `s`. – Igor Tandetnik Aug 27 '23 at 16:03
  • 1
    @HolyBlackCat It's not. A cast wouldn't have compiled. You can't cast a `std::string` to a function type. Besides, you can put any other name there, e.g. `f(xyz);`, and [it'd still compile](https://godbolt.org/z/xe66q5bej). – Igor Tandetnik Aug 27 '23 at 16:04
  • 1
    I stand corrected. – HolyBlackCat Aug 27 '23 at 16:08
  • 1
    I would use `using f = std::function;` (and pass the string by const &). And somthing like `std::declval().(s)` – Pepijn Kramer Aug 27 '23 at 16:10
  • 2
    I don't think redefinition of `s` as different kind of symbol is allowed. [Both Clang and MSVC reject your code](https://godbolt.org/z/f5Mxnvs5s). – 康桓瑋 Aug 27 '23 at 16:14
  • 1
    *why no compile time or runtime error are thrown* Because you haven't enable all and extra compiler warnings and turned them into errors. – 273K Aug 27 '23 at 16:14
  • @273K Yes, invoking g++ with -Wall produces warning ```unnecessary parentheses``` – vtm11 Aug 27 '23 at 16:17
  • @IgorTandetnik One thing I don't understand though - if it's taken as a forward declaration of a function, why doesn't it need a return type? – Paul Sanders Aug 27 '23 at 16:21
  • 2
    @PaulSanders What do you mean? The type of `s` is `f` - a function taking `std::string` and returning `void`. – Igor Tandetnik Aug 27 '23 at 16:26
  • @IgorTandetnik Ah yes, I see it now, thank you. – Paul Sanders Aug 28 '23 at 10:15

1 Answers1

3

As stated by commenters, f(s) is a declaration. The crucial wording is here:

There is an ambiguity in the grammar involving expression-statements and declarations: An expression-statement with a function-style explicit type conversion as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a (. In those cases the statement is considered a declaration, except as specified below.

[ Example 2:

class T { /* [...] */ };
// [...]
T(a);   //  declaration
// [...]

-- end example ]

- [stmt.ambig] p1

It doesn't matter what the type is (class, function, etc.), T(a) is a declaration if T names a type.

The declaration f(s) does nothing here because it is not used, and is equivalent to f s or void s(std::string). s is already a function parameter std::string s in this context, so GCC is wrong about allowing it. GCC also allows:

void fmistake(f func, std::string s) {
    void s(std::string);
}

// GCC also allows the following (in C++ mode)
void foo(int x) {
    // GCC rejects this as re-declaring a symbol in C, but not in C++ mode.
    void x(int);
}

This is a known GCC bug 52953, and has first been reported in 2012.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96