Declaring functions
A function declaration introduces the function name and its type. A function definition associates the function name/type with the function body.
Contents |
[edit] Function declaration
Function declarations may appear in any scope. A function declaration at class scope introduces a class member function (unless the friend specifier is used), see member functions and friend functions for details.
The type of the function being declared is composed from the return type (provided by the decl-specifier-seq of the declaration syntax) and the function declarator
noptr-declarator ( parameter-list ) cv(optional) ref(optional) except(optional) attr(optional)
|
(1) | ||||||||
noptr-declarator ( parameter-list ) cv(optional) ref(optional) except(optional) attr(optional) -> trailing
|
(2) | (since C++11) | |||||||
(see Declarations for the other forms of the declarator syntax)
auto
noptr-declarator | - | any valid declarator, but if it begins with *, &, or &&, it has to be surrounded by parentheses. |
parameter-list | - | possibly empty, comma-separated list of the function parameters (see below for details) |
attr(C++11) | - | optional list of attributes |
cv | - | const/volatile qualification, only allowed in non-static member function declarations |
ref(C++11) | - | ref-qualification, only allowed in non-static member function declarations |
except | - | either dynamic exception specification(deprecated) or noexcept specification(C++11) |
trailing(C++11) | - | Trailing return type, useful if the return type depends on argument names, such as template <class T, class U> auto add(T t, U u) -> decltype(t + u); or is complicated, such as in auto fpif(int)->int(*)(int) |
Function declarators can be mixed with other declarators, where decl-specifier-seq allows:
// declares an int, an int*, a function, and a pointer to a function int a = 1, *p = NULL, f(), (*pf)(double); // decl-specifier-seq is int // declarator f() declares (but doesn't define) // a function taking no arguments and returning int struct S { virtual int f(char) const, g(int) &&; // declares two non-static member functions virtual int f(char), x; // compile-time error: virtual (in decl-specifier-seq) // is only allowed in declarations of non-static // member functions };
Return type deduction (since C++14)If the decl-specifier-seq of the function declaration contains the keyword int x = 1; auto f() { return x; } // return type is int const auto& f() { return x; } // return type is const int& If the return type is int x = 1; decltype(auto) f() { return x; } // return type is int, same as decltype(x) decltype(auto) f() { return(x); } // return type is int&, same as decltype((x)) (note: " If there are multiple return statements, they must all deduce to the same type auto f(bool val) { if(val) return 123; // deduces return type int else return 3.14f; // deduces return type float: error } If there is no return statement, the type deduced is void auto f() { } // returns void auto* f() { } // error: cannot deduce auto* from void Once a return statement has been seen in a function, the return type deduced from that statement can be used in the rest of the function, including in other return statements. auto sum(int i) { if (i == 1) return i; // sum’s return type is int else return sum(i-1)+i; // OK, sum’s return type is already known } If the return statement uses a brace-init-list, deduction is not allowed: auto func () { return {1,2,3}; } // error Virtual functions cannot use return type deduction. struct F { virtual auto f() { return 2; } // error }; If a function uses return type deduction, it cannot be redeclared using the type that it deduces to, or another kind of return type deduction even if it deduces to the same type auto f(); // declared, not yet defined auto f() { return 42; } // defined, return type is int int f(); // error, cannot use the deduced type decltype(auto) f(); // error, different kind of deduction auto f(); // re-declared, OK template <typename T> struct A { friend T frf(T); }; auto frf(int i) { return i; } // not a friend of A<int> Function templates can use return type deduction. The deduction takes place at instantiation even if the expression in the return statement is not dependent. This instantiation is not in an immediate context for the purposes of SFINAE. template <class T> auto f(T t) { return t; } typedef decltype(f(1)) fint_t; // instantiates f<int> to deduce return type template<class T> auto f(T* t) { return *t; } void g() { int (*p)(int*) = &f; } // instantiates both fs to determine return types, // chooses second template overload Specializations of function templates that use return type deduction must use the same return type placeholders template <typename T> auto g(T t) { return t; } // #1 template auto g(int); // OK, return type is int //template char g(char); // error, no matching template template<> auto g(double); // OK, forward declaration with unknown return type template <typename T> T g(T t) { return t; } // OK, not equivalent to #1 template char g(char); // OK, now there is a matching template template auto g(float); // still matches #1 // void h() { return g(42); } // error, ambiguous Explicit instantiation declarations do not themselves instantiate function templates that use return type deduction template <typename T> auto f(T t) { return t; } extern template auto f(int); // does not instantiate f<int> int (*p)(int) = f; // instantiates f<int> to determine its return type, // but an explicit instantiation definition // is still required somewhere in the program |
(since C++14) |
[edit] Parameter list
Parameter list determines the arguments that can be specified when the function is called. It is a comma-separated list of parameter declarations, each of which has the following syntax
attr(optional) decl-specifier-seq declarator | (1) | ||||||||
attr(optional) decl-specifier-seq declarator = initializer
|
(2) | ||||||||
attr(optional) decl-specifier-seq abstract-declarator(optional) | (3) | ||||||||
attr(optional) decl-specifier-seq abstract-declarator(optional) = initializer
|
(4) | ||||||||
...
|
(5) | ||||||||
void
|
(6) | ||||||||
int f(int a, int *p, int (*(*x)(double))[3]);
int f(int a = 7, int *p = nullptr, int (*(*x)(double))[3] = nullptr);
int f(int, int *, int (*(*)(double))[3]);
int f(int = 7, int * = nullptr, int (*(*)(double))[3] = nullptr);
int printf(const char* fmt, ...);
void
cannot be used in a parameter list otherwise: int f(void, int); and int f(const void); are errors (although derived types, such as void*
can be used). In a template, only non-dependent void type can be used (a function taking a single parameter of type T
does not become a no-parameter function if instantiated with T = void
)Although decl-specifier-seq implies there can exist specifiers other than type specifiers, the only other specifier allowed is register as well as auto (until C++11), and it has virtually no effect.
Parameter names declared in function declarations are usually for only self-documenting purposes. They are used (but remain optional) in function definitions.
The type of each function parameter in the parameter list is determined according to the following rules:
Because of these rules, the following function declarations declare exactly the same function:
int f(char s[3]); int f(char[]); int f(char* s); int f(char* const); int f(char* volatile s);
The following declarations also declare exactly the same function
int f(int()); int f(int (*g)());
The comma before the ellipsis parameter that indicates variadic arguments is optional, even if it follows the ellipsis that indicates a parameter pack expansion, so the following function templates are exactly the same:
template<typename ...Args> void f(Args..., ...); template<typename ...Args> void f(Args... ...); template<typename ...Args> void f(Args......);
An example of when such declaration is used is the implementation of std::is_function
[edit] Function definition
A non-member function definition may appear at namespace scope only (there are no nested functions). A member function definition may also appear in the body of a class definition. They have the following syntax:
attr(optional) decl-specifier-seq(optional) declarator virt-specifier-seq(optional) function-body | |||||||||
where function-body is one of the following
ctor-initializer(optional) compound-statement | (1) | ||||||||
function-try-block | (2) | ||||||||
= delete ;
|
(3) | (since C++11) | |||||||
= default ;
|
(4) | (since C++11) | |||||||
attr(C++11) | - | optional list of attributes |
decl-specifier-seq | - | the return type with specifiers, as in the declaration grammar |
declarator | - | function declarator, same as in the function declaration grammar above |
virt-specifier-seq(C++11) | - | override, final, or their combination in any order (only allowed for member functions) |
ctor-initializer | - | member initializer list, only allowed in constructors |
compound-statement | - | the brace-enclosed sequence of statements that constututes the body of a function |
The parameter types, as well as the return type of a function cannot be incomplete class types, except for deleted functions (since C++14). An exception is made for member functions, which can use the class in which they are defined (or its enclosing class), even if it's incomplete at the point of definition.
This section is incomplete |
__func__Within the function body, the function-local predefined variable __func__ is defined as if by static const char __func__[] = "function-name"; This variable has block scope and static storage duration: struct S { S() : s(__func__) {} // OK: initializer-list is part of function body const char* s; }; void f(const char* s = __func__); // error: parameter-list is part of declarator |
(since C++11) |
[edit] Example 1: non-member functions
This section is incomplete Reason: move/cleanup |
#include <iostream> #include <string> // declaration in namespace(file) scope // (the definition is provided later) int f1(); // simple function with a default argument, returning nothing void f0(const std::string& arg = "world") { std::cout << "Hello, " << arg << '\n'; } // function returning a pointer to f0 auto fp11() -> void(*)(const std::string&) { return f0; } // function returning a pointer to f0, pre-C++11 style void (*fp03())(const std::string&) { return f0; } int main() { f0(); fp11()("test"); fp03()("again"); int f2(std::string); // declaration in function scope std::cout << f2("bad12") << '\n'; } // simple non-member function returning int int f1() { return 42; } // function with an exception specification and a function try block int f2(std::string str) noexcept try { return std::stoi(str); } catch(const std::exception& e) { std::cerr << "stoi() failed!\n"; return 0; }
Output:
Hello, world Hello, test Hello, again stoi() failed! 0