Question
Why does this C++ program fail when creating foo2 with the no-argument constructor, even though creating foo1 with the one-argument constructor works?
class Foo {
public:
Foo() {}
Foo(int a) {}
void bar() {}
};
int main() {
// Works: creates a Foo object using Foo(int).
Foo foo1(1);
foo1.bar();
// Fails: why is this not a Foo object?
Foo foo2();
foo2.bar();
return 0;
}
The compiler reports an error similar to:
request for member 'bar' in 'foo2', which is of non-class type 'Foo ()()'
Why does this happen, and how can a Foo object be correctly created with its no-argument constructor?
Short Answer
You will learn about the C++ most vexing parse: a grammar rule that makes Foo foo2(); a function declaration rather than construction of a Foo object. You will also learn the clearest modern ways to default-construct objects.
Concept
C++ allows declarations of both objects and functions to use similar syntax. When a statement can be interpreted in more than one valid way, C++ applies a historical parsing rule:
If something can be parsed as a function declaration, it is parsed as a function declaration.
Therefore, this line:
Foo foo2();
does not create a Foo object. It declares a function named foo2 that:
- takes no parameters, and
- returns a
Foo.
No function body is supplied, but declarations without definitions are allowed. The error occurs on the next line because foo2 is a function, not an object. A function has no member function named bar().
This confusing situation is traditionally called the most vexing parse. It matters because C++ code may look like object construction while silently declaring a function instead. Understanding it helps you read compiler diagnostics and choose unambiguous initialization syntax.
Mental Model
Think of C++ as reading a form that can describe either a thing or a recipe for making a thing.
Foo foo2;means: “Put aFooobject namedfoo2here.”Foo foo2();means: “Declare a recipe namedfoo2that takes no ingredients and produces aFoo.”
Even though empty parentheses look like “call the default constructor,” C++ treats them as part of a function declaration whenever that interpretation is possible.
To create the object, leave the parentheses out, or use braces.
Syntax and Examples
Use one of these unambiguous forms to call a default constructor.
Foo foo1; // Default initialization: calls Foo::Foo()
Foo foo2{}; // Value/list initialization: calls Foo::Foo()
Complete example:
#include <iostream>
class Foo {
public:
Foo() {
std::cout << "Default constructor\n";
}
Foo(int a) {
std::cout << "Constructor argument: " << a << "\n";
}
void bar() {
std::cout << "bar called\n";
}
};
int main() {
Foo first(1); // Calls Foo(int)
first.bar();
Foo second; // Calls Foo()
second.bar();
Foo third{}; // Also calls Foo()
third.bar();
}
Foo second; is the traditional and simple form for a local object. Foo third{}; is also widely used in modern C++ because braces avoid several ambiguous parsing cases.
Step by Step Execution
Consider this code:
class Foo {
public:
Foo() {}
void bar() {}
};
int main() {
Foo foo2();
foo2.bar();
}
Step by step:
- The compiler reads
Foo foo2();. - It sees a valid function-declaration pattern:
return_type function_name(parameter_list). - It interprets the line as: “Declare a no-argument function called
foo2that returnsFoo.” - No
Fooinstance is created, andFoo::Foo()is not called. - The compiler reads
foo2.bar(). foo2is known as a function, not an instance ofFoo.- Calling
.bar()on a function is invalid, so compilation fails.
Real World Use Cases
Default construction is common when an object can begin in a valid empty or initial state.
-
Configuration objects: Start with defaults, then change selected settings.
Config config{}; config.enableLogging = true; -
Streams and buffers: Create an empty object and fill it later.
std::string message; message += "Ready"; -
Data models: Construct an object before receiving data from a file, form, or API.
User user{}; // Populate user after parsing input. -
Containers: Create an empty container before adding elements.
std::vector<int> scores; scores.push_back(95);
In each case, use Type name; or Type name{};, not Type name();, when you intend to create an object.
Real Codebase Usage
In real C++ projects, developers usually avoid empty parentheses for local default construction entirely.
Prefer clear initialization
Session session{};
std::vector<int> ids;
Braces are especially helpful in generic code, where parenthesized initialization can become ambiguous.
Construct directly with required dependencies
If an object requires valid input, give it a constructor argument instead of allowing an incomplete default state.
class DatabaseConnection {
public:
explicit DatabaseConnection(const std::string& connectionString);
};
DatabaseConnection db{"server=localhost;database=app"};
Use factory functions for complex setup
A factory function can validate input and return a ready-to-use object.
Foo makeFoo() {
return Foo{};
}
Foo foo = makeFoo();
Use guard clauses after construction when validation is separate
Request request{};
if (!request.isValid()) {
;
}
(request);
Common Mistakes
Writing empty parentheses for a default constructor
Broken code:
Foo foo();
foo.bar();
This declares a function. Fix it with either:
Foo foo;
// or
Foo foo{};
Assuming the compiler will call the default constructor later
This declaration does not create an object now or later:
Foo createFoo();
It only announces that a function with that name exists. If you later try to call it, it must have a definition:
Foo createFoo() {
return Foo{};
}
int main() {
Foo foo = createFoo();
}
Confusing a function declaration with a function call
A function call needs an expression to call. For example:
Foo makeFoo() {
Foo{};
}
{
Foo foo = ();
}
Comparisons
| Syntax | What it means for a class type | Creates an object? |
|---|---|---|
Foo foo; | Default-initializes foo; calls Foo() when it exists | Yes |
Foo foo{}; | List/value-initializes foo; calls Foo() when it exists | Yes |
Foo foo(1); | Direct-initializes foo with 1; calls Foo(int) | Yes |
Foo foo = Foo{}; | Creates from a temporary Foo{}; usually optimized |
Cheat Sheet
// Default-construct a local object
Foo foo;
Foo foo{};
// Construct with an argument
Foo foo(42);
Foo foo{42};
// Do NOT use this to create an object
Foo foo(); // Declares: Foo foo() -- a function returning Foo
- C++ parses an ambiguous declaration as a function declaration when possible.
Type name();is a no-parameter function declaration, not default construction.- Use
Type name;for traditional default construction. - Use
Type name{};for unambiguous brace initialization. - The compiler message mentioning a non-class type such as
Foo ()()is a strong clue that you declared a function accidentally.
FAQ
Why is Foo foo2(); a function declaration in C++?
It matches valid function-declaration syntax: it declares foo2 as a function taking no parameters and returning Foo. C++ chooses that interpretation when the syntax is ambiguous.
How do I call a default constructor in C++?
For a local object, write either:
Foo foo;
Foo foo{};
Both construct a Foo using its no-argument constructor when one is available.
Is Foo foo; the same as Foo foo{};?
For many class types with a user-provided default constructor, they have the same result. However, initialization details differ for fundamental types and aggregate types. Braces also reject narrowing conversions.
What does “most vexing parse” mean?
It is the informal name for cases where C++ syntax that looks like object construction is parsed as a function declaration instead.
Why does the error say Foo ()()?
That type description represents a function taking no arguments and returning Foo. It reveals that the compiler interpreted foo2 as a function.
Can I write Foo foo = Foo(); instead?
Mini Project
Description
Build a small Task class that supports both default construction and construction with a task name. The program will create tasks safely, display them, and demonstrate the difference between correct object initialization and the most vexing parse.
Goal
Create and use default-constructed and argument-constructed Task objects without accidentally declaring a function.
Requirements
Use a Task class with a default constructor and a constructor that accepts a task name.
Add a member function that prints the task name and completion status.
Create one task with default construction using braces or no parentheses.
Create one task by passing a name to its constructor.
Call the display member function on both objects.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.