Functions in C++: Declarations, Parameters, Overloading and Modern Techniques

How C++ functions receive arguments and what each way costs, why overloads and defaults fail to compile, and the dangling return that builds anyway.

A sheet of dough feeding into a hand-crank pasta machine and coming out as strands, representing a C++ function that takes an input and returns a transformed result.

Call show(next_ticket(), next_ticket()) in C++, where each call returns the next number, and GCC prints first = 2, second = 1. Clang prints first = 1, second = 2. Both are correct, because C++ does not specify the order in which a function’s arguments are evaluated. Most of what makes C++ functions different from functions in other languages is like this: a precise rule that the compiler follows exactly, whether or not you know it is there.

This guide covers C++ functions from declaration to modern techniques: how to declare and define them, the five ways an argument can reach a function and what each one costs, return values, default arguments, overloading, variadic templates, lambdas, constexpr, and the reference-to-a-local bug that compiles with only a warning. For the C side of the topic, see functions in C. Every program was compiled and run for this article on Ubuntu 24.04 with GCC 13.3 and Clang 18.1.3 under -std=c++17 and -std=c++20 with -Wall -Wextra -pedantic, and the correct programs run clean under AddressSanitizer and UndefinedBehaviorSanitizer; the examples that must not compile are shown with the errors both compilers gave. All output is captured verbatim, and the code is in a GitHub repository whose build repeats these checks on each commit.

The Short Answer

How do you pass an object to a C++ function without copying it? Take it by const reference: void print(const Order& order). The function reads the caller’s object directly. Passing by value made one copy in the test below; passing by const& made none.

Can two C++ functions have the same name? Yes, if their parameter lists differ. This is function overloading, and the compiler picks a version from the argument types. Functions that differ only in their return type cannot be overloaded.

What replaces C-style ... variable arguments in C++? A variadic template, usually with a fold expression (C++17), or std::initializer_list when every argument has the same type. Both are checked by the compiler; va_arg is not.

What Is a Function in C++?

A function in C++ is a named block of code with a declared return type and parameter list. It runs when called, receives its arguments according to its parameter types (as copies, references or pointers), and can return a value. Functions can be overloaded by parameter types, have default arguments, be templates, and be evaluated at compile time when declared constexpr.

Every function has the same parts. Using int box_volume(int length, int width = 2, int height = 3) as the example:

PartIn the exampleWhat it does
Return typeintThe type of the value the call produces; void if none
Namebox_volumeHow callers refer to it; may be shared by several overloads
Parameter list(int length, int width = 2, int height = 3)The types the function accepts, with optional default arguments
Body{ ... }The statements that run; return ends the call and supplies the value
Signaturename plus parameter typesWhat overload resolution uses; for an ordinary function the return type is not part of it

The complete grammar, including trailing return types (auto f() -> int) and attributes, is in cppreference’s function declaration page.

Declaring and Defining a Function

A declaration tells the compiler a function exists and what its signature is. A definition is a declaration plus a body. A function must be declared before the point where it is called, and it can be declared many times but defined only once in the program.

// declare_define.cpp - a declaration lets main() call a function that is
// defined further down the file.
#include <iostream>

double circle_area(double radius);   // declaration (prototype)

int main() {
    std::cout << "area of r=2: " << circle_area(2.0) << '\n';
    std::cout << "area of r=0.5: " << circle_area(0.5) << '\n';
}

// definition: the declaration's signature plus a body
double circle_area(double radius) {
    constexpr double pi = 3.14159265358979;
    return pi * radius * radius;
}

Output:

area of r=2: 12.5664
area of r=0.5: 0.785398

main() calls circle_area before its body appears, which works because the declaration on line 5 came first. Remove that line and both compilers reject the call:

// undeclared_call.cpp - must not compile: area() is used before any
// declaration of it.
#include <iostream>

int main() {
    std::cout << area(3.0) << '\n';
}

double area(double side) { return side * side; }

GCC first, then Clang:

tests/compile-fail/undeclared_call.cpp:6:18: error: 'area' was not declared in this scope
tests/compile-fail/undeclared_call.cpp:6:18: error: use of undeclared identifier 'area'

In a program with several source files, declarations go in a header (geometry.h) that every file using the function includes, and the definition goes in one .cpp file. That split is why the rule exists: each file is compiled on its own, and the declaration is the only thing a file sees of a function defined elsewhere.

Passing Arguments: Value, Reference, const Reference and Pointer

How a parameter is declared decides what the function receives: its own copy, a second name for the caller’s object, or the object’s address. To make the difference visible, this program passes a Tracked object, which counts how many times it is copied, through each kind of parameter:

// passing.cpp - count the copies each way of passing an argument makes.
#include <iostream>
#include <string>

struct Tracked {
    std::string name;
    static inline int copies = 0;

    explicit Tracked(std::string n) : name(std::move(n)) {}
    Tracked(const Tracked& other) : name(other.name) { ++copies; }
    Tracked& operator=(const Tracked& other) { name = other.name; ++copies; return *this; }
};

void by_value(Tracked t)            { t.name += "!"; }   // works on a copy
void by_const_ref(const Tracked& t) { (void)t.name.size(); }
void by_ref(Tracked& t)             { t.name += "!"; }   // changes the caller's object
void by_pointer(Tracked* t)         { if (t) t->name += "?"; }

Tracked make(const std::string& n)  { return Tracked{n}; }

int main() {
    Tracked doc{"report"};

    auto measure = [&](const char* label, auto call) {
        Tracked::copies = 0;
        call();
        std::cout << label << "copies: " << Tracked::copies << "  name: " << doc.name << '\n';
    };

    measure("by value:           ", [&] { by_value(doc); });
    measure("by const reference: ", [&] { by_const_ref(doc); });
    measure("by reference:       ", [&] { by_ref(doc); });
    measure("by pointer:         ", [&] { by_pointer(&doc); });
    measure("null pointer:       ", [&] { by_pointer(nullptr); });

    Tracked::copies = 0;
    Tracked fresh = make("draft");
    std::cout << "returned by value:  copies: " << Tracked::copies << "  name: " << fresh.name << '\n';
}

Output:

by value:           copies: 1  name: report
by const reference: copies: 0  name: report
by reference:       copies: 0  name: report!
by pointer:         copies: 0  name: report!?
null pointer:       copies: 0  name: report!?
returned by value:  copies: 0  name: draft
Five ways an argument reaches a function Copies of a Tracked object counted by passing.cpp, GCC 13.3 and Clang 18 parameter, and what the function gets use it for copies void f(Tracked t) doc copy t its own copy small types: int, double, string_view; or when f keeps a copy anyway 1 void f(const Tracked& t) doc t refers to the caller’s object, read-only reading anything bigger than a few machine words 0 void f(Tracked& t) doc t refers to the caller’s object, writable values the caller expects the function to change 0 void f(Tracked* t) doc &doc or nullptr an address that may be null an argument that may be absent; C interfaces 0 Tracked make() make() fresh a result built in place (C++17) results, including large objects 0 C++17 guarantees the in-place return: 0 copies even with -O0 -fno-elide-constructors.
Only passing by value made a copy. The two reference forms and the pointer let the function work on the caller’s object directly, and the function that returned by value built its result in place. The counts are the ones passing.cpp printed above.
  • By value (Tracked t): the function got its own copy, so its "!" was added to the copy and the caller’s object still said report. One copy; for an object that owns heap memory, such as a long std::string, a copy also means an allocation.
  • By const reference (const Tracked& t): no copy, and the compiler stops the function from modifying the object. This is the default for anything larger than a few machine words.
  • By reference (Tracked& t): no copy, and the change is visible to the caller: the name became report!. Use it when changing the caller’s object is the point of the function.
  • By pointer (Tracked* t): no copy, and the argument can be nullptr, which the function must check. The second pointer call passed nullptr and nothing changed. Prefer a reference unless “no object” is a meaningful input. Pointers themselves are covered in pointers in C++.

Small, cheap-to-copy types such as int, double and std::string_view are passed by value. For read-only text, a std::string_view parameter accepts string literals and std::string objects without building a temporary std::string; the measured difference is in C++ strings.

A non-const reference parameter also says something to the caller: “I will change this.” The compiler enforces that by refusing to bind one to a temporary value, because a change to a temporary would be lost:

// temporary_to_ref.cpp - must not compile: a non-const reference parameter
// cannot bind to a temporary value.
void add_bonus(int& score) { score += 10; }

int main() {
    add_bonus(5);
}

GCC first, then Clang (Clang’s note is the clearer explanation):

tests/compile-fail/temporary_to_ref.cpp:6:15: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
tests/compile-fail/temporary_to_ref.cpp:6:5: error: no matching function for call to 'add_bonus'
tests/compile-fail/temporary_to_ref.cpp:3:6: note: candidate function not viable: expects an lvalue for 1st argument

A const int& parameter accepts 5, because the function promises not to change it.

Returning Values

The last line of the passing program returned a Tracked by value and made zero copies. Since C++17, when a function returns a temporary object of its return type (return Tracked{n};), the object is created directly in the caller’s variable. This is guaranteed, not an optimization: GCC still reported zero copies with -O0 -fno-elide-constructors under -std=c++17, and two copies with the same flags under -std=c++14. Returning large objects by value is the normal way to return them.

For more than one result, return a struct and unpack it with a structured binding. For a result that may not exist, return std::optional:

// returning.cpp - returning several values, or a value that may be missing.
#include <iostream>
#include <optional>
#include <string_view>

struct MinMax {
    int min;
    int max;
};

MinMax min_max(std::initializer_list<int> values) {
    MinMax r{*values.begin(), *values.begin()};
    for (int v : values) {
        if (v < r.min) r.min = v;
        if (v > r.max) r.max = v;
    }
    return r;
}

// No sensible int to return for bad input, so say so in the return type.
std::optional<int> parse_digit(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    return std::nullopt;
}

int main() {
    auto [low, high] = min_max({4, -2, 9, 0});   // structured binding (C++17)
    std::cout << "min " << low << ", max " << high << '\n';

    for (char c : std::string_view("7x")) {
        if (auto d = parse_digit(c))
            std::cout << c << " -> " << *d << '\n';
        else
            std::cout << c << " -> not a digit\n";
    }
}

Output:

min -2, max 9
7 -> 7
x -> not a digit

parse_digit has no valid int to return for 'x', and std::optional<int> says so in the type: the caller has to test it before using the value. The older alternatives, a special value such as -1 or an output parameter plus a bool, both rely on the caller remembering a convention.

Default Arguments

A default argument is used when the caller leaves that argument out. Defaults can only be given to trailing parameters, and they belong in the declaration:

// defaults.cpp - default arguments fill in trailing parameters.
#include <iostream>

// The defaults belong in the declaration, and only there.
int box_volume(int length, int width = 2, int height = 3);

int main() {
    std::cout << box_volume(10, 12, 15) << '\n';   // nothing defaulted
    std::cout << box_volume(10, 12) << '\n';       // height = 3
    std::cout << box_volume(10) << '\n';           // width = 2, height = 3
}

int box_volume(int length, int width, int height) {
    std::cout << length << " x " << width << " x " << height << " = ";
    return length * width * height;
}

Output:

10 x 12 x 15 = 1800
10 x 12 x 3 = 360
10 x 2 x 3 = 60

The compiler fills in the missing arguments at the call site, so box_volume(10) compiles exactly as box_volume(10, 2, 3). Two rules follow, and both compilers enforce them. A default must not be repeated in the definition, even with the same value:

// default_redefined.cpp - must not compile: the default argument is repeated
// in the definition.
int box_volume(int length, int width = 2, int height = 3);

int box_volume(int length, int width = 2, int height = 3) {
    return length * width * height;
}

int main() { return box_volume(1) == 6 ? 0 : 1; }

GCC first, then Clang:

tests/compile-fail/default_redefined.cpp:5:5: error: default argument given for parameter 2 of 'int box_volume(int, int, int)' [-fpermissive]
tests/compile-fail/default_redefined.cpp:5:32: error: redefinition of default argument

And every parameter after one with a default must have a default too, because there is no way to skip an argument in the middle of a call:

// default_not_trailing.cpp - must not compile: a parameter with a default
// is followed by one without.
int box_volume(int length = 1, int width, int height);

int main() { return 0; }
tests/compile-fail/default_not_trailing.cpp:3:36: error: default argument missing for parameter 2 of 'int box_volume(int, int, int)'
tests/compile-fail/default_not_trailing.cpp:3:36: error: missing default argument on parameter 'width'

Because defaults are compiled into each call, changing a default in a header changes nothing for code that is not recompiled. In a library’s public interface, an overload (box_volume(int length) calling the three-argument version) avoids that. The details are on cppreference’s default arguments page.

Function Overloading

Several functions can share a name if their parameter lists differ. At each call, the compiler ranks the candidates by how well the argument types match and calls the one that matches most closely; if no candidate is a closer match than all the others, the call does not compile.

// overloading.cpp - the compiler picks an overload from the argument types.
#include <iostream>
#include <string_view>

void describe(int value)              { std::cout << "int:         " << value << '\n'; }
void describe(double value)           { std::cout << "double:      " << value << '\n'; }
void describe(std::string_view value) { std::cout << "string_view: " << value << '\n'; }

int main() {
    describe(42);        // exact match: int
    describe(4.5);       // exact match: double
    describe('A');       // char is promoted to int
    describe(2.5f);      // float is promoted to double
    describe(true);      // bool is promoted to int
    describe("text");    // const char* converts to string_view
}

Output:

int:         42
double:      4.5
int:         65
double:      2.5
int:         1
string_view: text

The first two calls are exact matches. The next three are not, and they show the ranking: a char and a bool are promoted to int, and a float is promoted to double, so describe('A') printed 65 and describe(true) printed 1. A promotion ranks above a conversion, which is why describe(2.5f) chose double without any ambiguity. The string literal converts to std::string_view, the only candidate that accepts it.

Two things do not work. The return type is not part of a function’s signature, so two declarations that differ only in what they return are a redeclaration, not an overload:

// return_type_only.cpp - must not compile: overloads cannot differ only in
// their return type.
int parse(const char* text);
double parse(const char* text);

int main() { return 0; }

GCC first, then Clang:

tests/compile-fail/return_type_only.cpp:4:8: error: ambiguating new declaration of 'double parse(const char*)'
tests/compile-fail/return_type_only.cpp:4:8: error: functions that differ only in their return type cannot be overloaded

And when two candidates need conversions of the same rank, neither wins. 2.5 is a double; turning it into an int and turning it into a float are both standard conversions:

// ambiguous_call.cpp - must not compile: 2.5 is a double, and converting it
// to int or to float ranks the same, so neither overload is better.
void scale(int factor);
void scale(float factor);

int main() {
    scale(2.5);
}

GCC first, then Clang:

tests/compile-fail/ambiguous_call.cpp:7:10: error: call of overloaded 'scale(double)' is ambiguous
tests/compile-fail/ambiguous_call.cpp:7:5: error: call to 'scale' is ambiguous

Overloading is also how C++ gives operators meaning for user-defined types, covered in operator overloading in C++. When the overloads would all have the same body for different types, write one function template instead; see C++ templates.

Argument Evaluation Order Is Unspecified

The order in which a function’s arguments are evaluated is unspecified. This program calls a function with side effects twice in one argument list:

// eval_order.cpp - the order in which a call's arguments are evaluated is
// unspecified; GCC and Clang choose differently.
#include <iostream>

int next_ticket() {
    static int ticket = 0;
    return ++ticket;
}

void show(int first, int second) {
    std::cout << "first = " << first << ", second = " << second << '\n';
}

int main() {
    show(next_ticket(), next_ticket());

    // Portable: evaluate into named variables, in the order you need.
    const int a = next_ticket();
    const int b = next_ticket();
    show(a, b);
}

Output with GCC 13.3:

first = 2, second = 1
first = 3, second = 4

Output with Clang 18.1.3:

first = 1, second = 2
first = 3, second = 4

Same source, same flags, different first line. GCC evaluated the second argument first; Clang evaluated left to right. Neither compiler warned under -Wall -Wextra. C++17 tightened the rules so that each argument is evaluated completely before the next one starts (they are indeterminately sequenced), which removed the undefined behavior of interleaved evaluation, but it did not fix an order. The second line is the portable version: evaluate into named variables first, in the order the program needs. The rule and its C++17 changes are listed on cppreference’s order of evaluation page.

Variable Numbers of Arguments

C++ inherits C’s ... parameters and the va_list macros, but nothing checks that the arguments match what the function reads with va_arg; a mismatch is undefined behavior. C++ has two type-checked replacements:

// variadic.cpp - type-checked ways to accept any number of arguments.
#include <initializer_list>
#include <iostream>
#include <string>

// All arguments of one type: std::initializer_list.
double average(std::initializer_list<double> values) {
    double total = 0;
    for (double v : values) total += v;
    return values.size() ? total / static_cast<double>(values.size()) : 0.0;
}

// Arguments of any types: a variadic template with a fold expression (C++17).
template <typename... Args>
void print_all(const Args&... args) {
    ((std::cout << args << ' '), ...);
    std::cout << "(" << sizeof...(args) << " arguments)\n";
}

template <typename... Nums>
auto sum(Nums... nums) {
    return (nums + ... + 0);
}

int main() {
    std::cout << "average: " << average({5, 7, 6, 8}) << '\n';
    print_all(1, 2.5, 'c', std::string("four"));
    print_all();
    std::cout << "sum: " << sum(1, 2, 3, 4) << '\n';
}

Output:

average: 6.5
1 2.5 c four (4 arguments)
(0 arguments)
sum: 10
  • std::initializer_list<T> suits “any number of the same type”: average({5, 7, 6, 8}). The braces build the list, and every element must convert to double.
  • A variadic template (template <typename... Args>) accepts any number of arguments of any types. sizeof...(args) gives the count, and a fold expression (C++17) applies an operator across the whole pack: (nums + ... + 0) expands to 1 + (2 + (3 + (4 + 0))). Each call instantiates a function for exactly those argument types, so a type error is a compile error.

C-style variadic functions remain the way to call C interfaces such as printf; functions in C covers how they work.

Functions as Values: Pointers, Lambdas and std::function

A function can be passed to another function. Many standard algorithms work this way: std::count_if takes a predicate and calls it for each element.

// function_values.cpp - functions passed to other functions: a function
// pointer, a lambda, and std::function.
#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>

bool is_even(int n) { return n % 2 == 0; }

int apply_twice(const std::function<int(int)>& f, int x) { return f(f(x)); }

int main() {
    const std::vector<int> v{3, 8, 5, 12, 7, 6};

    // 1. A named function, passed as a pointer.
    std::cout << "even:      " << std::count_if(v.begin(), v.end(), is_even) << '\n';

    // 2. A lambda that captures a local variable.
    int limit = 6;
    std::cout << "above " << limit << ":   "
              << std::count_if(v.begin(), v.end(), [limit](int n) { return n > limit; }) << '\n';

    // 3. std::function stores any callable with a matching signature.
    std::cout << "twice +10: " << apply_twice([](int n) { return n + 10; }, 1) << '\n';
    std::cout << "twice *3:  " << apply_twice([](int n) { return n * 3; }, 2) << '\n';
}

Output:

even:      3
above 6:   3
twice +10: 21
twice *3:  18
  1. A function pointer. Writing is_even without parentheses produces a pointer to the function, which count_if calls for each element.
  2. A lambda is an unnamed function written where it is used. [limit] copies the local variable into the lambda; a function pointer cannot carry state like that, which is the main reason lambdas replaced hand-written predicate functions.
  3. std::function<int(int)> can hold any callable with that signature: a function pointer, a lambda, or an object with operator(). It is the right parameter type when a function must store a callback or accept callables of different types at run time. It can allocate and adds an indirect call, so when the callable is only called, not stored, a template parameter (template <typename F> int apply_twice(F f, int x)) avoids both.

constexpr and inline Functions

A constexpr function can run at compile time when its arguments are constants, and at run time otherwise. The same factorial does both here:

// constexpr_fn.cpp - one function, evaluated at compile time or at run time.
#include <iostream>

constexpr long long factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

static_assert(factorial(5) == 120);     // checked by the compiler
constexpr long long table_size = factorial(10);

int main(int argc, char*[]) {
    const int n = argc + 5;              // known only when the program runs
    std::cout << n << "! = " << factorial(n) << " (run time)\n";
    std::cout << "10! = " << table_size << " (compile time)" << '\n';
}

Output:

6! = 720 (run time)
10! = 3628800 (compile time)

static_assert(factorial(5) == 120) is checked during compilation: if it were false, the program would not build. table_size is computed by the compiler, while factorial(n) runs normally because n depends on argc. factorial is also recursive, a function calling itself until it reaches a base case; Fibonacci in C and C++ compares recursion with loops and memoization.

inline is often read as “make this call faster”, but in modern C++ its effect is about linking: an inline function may be defined in a header that several source files include, without a multiple-definition error. Whether a call is actually inlined is the optimizer’s decision, with or without the keyword. constexpr functions and functions defined inside a class body are implicitly inline.

Returning a Reference to a Local Variable

Local variables are destroyed when their function returns. Returning a reference or pointer to one hands the caller something that no longer exists:

// dangling_return.cpp - returning a reference to a local variable.
// Compiles with a warning; calling it is undefined behavior.
#include <string>

const std::string& greeting(const std::string& name) {
    std::string text = "Hello, " + name;
    return text;   // text is destroyed when the function returns
}

int main() {
    return greeting("Ada").empty() ? 1 : 0;
}

Both compilers warn under -Wall (GCC first, then Clang):

src/dangling_return.cpp:7:12: warning: reference to local variable 'text' returned [-Wreturn-local-addr]
src/dangling_return.cpp:7:12: warning: reference to stack memory associated with local variable 'text' returned [-Wreturn-stack-address]

It is only a warning, so the program builds, and what it does next differs. The GCC build crashed with a segmentation fault at both -O0 and -O2; GCC replaces the returned reference with a null pointer. The Clang build ran and exited with status 0, as if nothing were wrong. Built with AddressSanitizer, the Clang version stopped at the read (addresses shortened):

==PID==ERROR: AddressSanitizer: stack-use-after-return on address 0x... at pc 0x... bp 0x... sp 0x...
READ of size 8 at 0x... thread T0

The fix is to return by value, std::string greeting(const std::string& name), which costs nothing extra because the returned object is built in place. Treat -Wreturn-local-addr and -Wreturn-stack-address as errors (-Werror), since there is no correct program that triggers them.

Key Takeaways

  • Pass by const& to read, by & to modify, by value for small types. Passing by value made one copy in the test; the reference forms made none.
  • Return by value. Since C++17 a returned temporary is built in the caller’s variable: zero copies, even with optimization off.
  • Overloads differ by parameters, not return type. char, bool and float arguments are promoted before conversions are considered, and a tie between conversions is a compile error.
  • Default arguments go in the declaration only, on trailing parameters, and are compiled into each call.
  • Do not rely on argument evaluation order. GCC and Clang evaluated the same two arguments in opposite orders.
  • Use variadic templates or std::initializer_list instead of ..., so the compiler checks every argument.
  • Never return a reference to a local. The GCC build crashed, and the Clang build exited normally with the same bug.

Frequently Asked Questions

Conclusion

Most C++ function bugs are not syntax errors. They are calls that compile and do something other than what the reader expects: a copy where a reference was meant, an overload chosen by a promotion, arguments evaluated in an order nobody specified, a reference to an object that no longer exists. The compiler catches some of these as errors, warns about others, and says nothing about the rest, which is why the parameter type is a decision worth making deliberately every time.

Functions are also where the rest of C++ starts: member functions in classes, operator overloads, templates and lambdas all follow the rules above. The C++ programming tutorials are collected in the C++ section.

Source Code and Tests

functions

All programs on this page are in the basics/functions directory of the MYCPLUS C++ examples repository.

Build and test:

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
bash tests/run_tests.sh g++ c++17
bash tests/sanitizers.sh g++

What the build checks. On Linux, with GCC and Clang under C++17 and C++20, it builds every program with -Wall -Wextra -pedantic -Werror, runs it and compares its output with this page, using GCC’s or Clang’s expected output for eval_order according to the compiler; checks that dangling_return.cpp still produces the warning quoted here; and checks that the six compile-fail examples are rejected with the expected error. A sanitizer job confirms that every correct program runs clean under AddressSanitizer and UndefinedBehaviorSanitizer and that AddressSanitizer reports the dangling reference. A macOS job runs the same tests with Apple Clang, and a Windows job builds every program with MSVC at /W4 /WX and compares the output, printing MSVC’s eval_order result without comparing it. The plain-run crash and exit status of dangling_return and the C++14 copy count were measured locally and are not part of the build.

Scroll to Top