C++14 Features
Overview of C++14 language and library additions: generic lambdas, variable templates, relaxed constexpr, return type deduction, and more.
C++14since C++14C++14 is a minor revision of C++11, published in December 2014, that filled in gaps and relaxed restrictions rather than introducing sweeping new paradigms.
Overview
C++14 arrived three years after C++11 and deliberately kept scope narrow. Where C++11 rewrote the language, C++14 polished it β lifting artificial restrictions, adding missing utilities (std::make_unique was simply forgotten in C++11), and making template and lambda machinery more ergonomic. Almost every feature here is something C++11 users wished they had the day they shipped their first modern-C++ codebase.
The standard is split into language changes and library additions. Together they represent a net reduction in boilerplate without any conceptual overhead.
Language Features
Return Type Deduction for Functions
C++11 required a trailing return type when using auto. C++14 removes that requirement: the compiler deduces the return type from the return expression directly.
// C++11 required
auto divide(double a, double b) -> double { return a / b; }
// C++14: trailing type unnecessary
auto divide(double a, double b) { return a / b; }
// Works for templates too β the key practical win
template <typename N, typename D>
auto safe_divide(N num, D den) {
return num / den; // deduced at instantiation
}Multiple return statements are fine as long as they deduce to the same type. Recursive functions require at least one return that does not recurse to appear first.
decltype(auto) is also introduced in C++14. It preserves reference and cv-qualifiers from the return expression, unlike plain auto which strips them:
int x = 42;
int& ref = x;
auto f1() { return ref; } // returns int (copy)
decltype(auto) f2() { return ref; } // returns int& (reference preserved)Generic Lambdas
C++14 allows auto in lambda parameter lists, effectively turning the lambda into a function-object template. This was one of the most requested features because it eliminates boilerplate for single-use local algorithms.
auto add = [](auto a, auto b) { return a + b; };
add(1, 2); // int
add(1.5, 2.5); // double
add(std::string{"foo"}, "bar"); // std::string
// Practical: sort by any comparable field
std::vector<std::pair<int,std::string>> v = {{3,"c"},{1,"a"},{2,"b"}};
std::sort(v.begin(), v.end(), [](auto& lhs, auto& rhs){
return lhs.first < rhs.first;
});Lambda Init-Capture (Generalized Lambda Capture)
C++11 lambdas could capture by value or by reference but could not move-capture or introduce new names. C++14 generalises the capture clause:
// Move-capture: transfer ownership into the lambda
auto ptr = std::make_unique<Widget>();
auto f = [p = std::move(ptr)]() { p->process(); };
// Rename on capture
int total = 0;
auto accumulate = [sum = total](int x) mutable { sum += x; return sum; };
// Compute a value at capture time
auto offset_add = [base = compute_base()](int x) { return base + x; };This makes lambdas fully viable for ownership-transferring callbacks (e.g. posting work to a thread pool).
Variable Templates
C++14 lets you parameterise a variable declaration on a template parameter. The primary use case before C++17 _v helpers was mathematical constants and type-trait shorthands.
template <typename T>
constexpr T pi = T(3.141592653589793238462643383L);
// Use at the right precision automatically
float area_f = pi<float> * r * r;
double area_d = pi<double> * r * r;
// Library-style: expose a trait value without ::value noise
template <typename T>
constexpr bool is_integral_v = std::is_integral<T>::value; // C++14 pattern
// (C++17 standardised this as std::is_integral_v<T>)Relaxed constexpr Functions
C++11 constexpr functions were limited to a single return expression β no local variables, no loops, no if statements. C++14 removes almost all of those restrictions.
// C++11: forced into recursive ternary gymnastics
constexpr int factorial_11(int n) {
return n <= 1 ? 1 : n * factorial_11(n - 1);
}
// C++14: just write normal code
constexpr int factorial_14(int n) {
int result = 1;
for (int i = 2; i <= n; ++i)
result *= i;
return result;
}
static_assert(factorial_14(10) == 3628800, "");Mutable local variables, loops, and conditionals are all permitted. virtual functions and try/catch remain disallowed until C++20.
Binary Literals and Digit Separators
// Binary literals (C++14)
constexpr uint8_t mask = 0b1010'1100; // 0xAC
// Digit separators: single quote as visual grouping
constexpr long population = 8'100'000'000L;
constexpr double avogadro = 6.022'140'76e23;
constexpr uint32_t ipv4 = 0xC0'A8'00'01; // 192.168.0.1The separator has no semantic effect; it exists purely for readability.
Aggregate Initialization with Default Member Initializers
C++11 allowed default non-static member initializers but accidentally excluded aggregates from direct-list-initialization when those defaults were present. C++14 fixes that:
struct Config {
int timeout = 30;
bool verbose = false;
std::string host = "localhost";
};
Config c1{}; // all defaults
Config c2{60}; // timeout=60, rest default
Config c3{10, true}; // timeout=10, verbose=true, host="localhost"Library Features
std::make_unique
The most embarrassing omission from C++11 was std::make_unique. C++14 adds it, completing the smart-pointer factory idiom.
// Before C++14: raw new visible, exception-unsafe in some call sites
std::unique_ptr<Widget> w(new Widget(arg1, arg2));
// C++14
auto w = std::make_unique<Widget>(arg1, arg2);
auto arr = std::make_unique<int[]>(1024); // array formPrefer make_unique over new: it is exception-safe, avoids repeating the type, and clearly expresses intent.
Type Trait Aliases (_t suffixes)
C++14 adds using alias templates for every type-transforming trait, eliminating ::type noise:
// C++11
typename std::remove_reference<T>::type
typename std::add_const<T>::type
// C++14
std::remove_reference_t<T>
std::add_const_t<T>
std::enable_if_t<condition, T> // especially valuable in SFINAE_v variable templates for value traits (std::is_integral_v<T>) were not standardised until C++17, though you can define your own with the variable template syntax described above.
Heterogeneous Lookup and Transparent Functors
std::less<void> (and other comparison functors with void specialisation) performs transparent comparison: no implicit conversion to the key type. Associative containers accept a comparator type as a third template argument, enabling find/lower_bound without constructing a key object.
std::set<std::string, std::less<>> names = {"alice", "bob", "carol"};
// C++11: constructs a std::string from the literal
names.find(std::string{"alice"});
// C++14: compares directly, no allocation
names.find("alice"); // const char* compared heterogeneouslystd::integer_sequence / std::index_sequence
Utility for template metaprogramming with parameter packs and tuple manipulation:
template <typename Tuple, std::size_t... Is>
void print_tuple_impl(const Tuple& t, std::index_sequence<Is...>) {
((std::cout << std::get<Is>(t) << ' '), ...); // fold, C++17 syntax
}
template <typename... Ts>
void print_tuple(const std::tuple<Ts...>& t) {
print_tuple_impl(t, std::index_sequence_for<Ts...>{});
}std::exchange
Atomically replaces a value and returns the old one β useful for move constructors and state machines:
// Manual swap pattern
T old = std::move(obj.resource);
obj.resource = nullptr;
// With std::exchange
T old = std::exchange(obj.resource, nullptr);
// Move constructor pattern
MyType(MyType&& other) noexcept
: data_(std::exchange(other.data_, nullptr))
, size_(std::exchange(other.size_, 0)) {}User-Defined Literals for Standard Types
using namespace std::literals;
auto s = "hello"s; // std::string
auto ms = 250ms; // std::chrono::milliseconds
auto s2 = 1s; // std::chrono::seconds
auto z = 3.0 + 4.0i; // std::complex<double>Common Pitfalls
Return type deduction and decltype(auto) confusion. Use plain auto when you want a value return; use decltype(auto) only when you need to propagate reference or cv-qualifiers β typically in generic forwarding wrappers.
Init-capture copies, not moves, by default. [x = expr] copies expr into x. To move: [x = std::move(expr)]. Forgetting std::move with move-only types causes a compile error, but forgetting it with copyable types silently copies.
Relaxed constexpr does not imply consteval. A C++14 constexpr function can still be called at runtime. If an argument is not a constant expression, evaluation happens at runtime with no error. Use static_assert to verify compile-time evaluation when correctness depends on it.
_t aliases require <type_traits>. Unlike the ::type members which have always been there, the alias templates are new in C++14 and require an up-to-date include and -std=c++14 or later.
See Also
reference/language/cpp11-featuresβ the major revision that preceded C++14reference/language/cpp17-featuresβ standardises_vtrait variables,if constexpr, structured bindingsreference/language/cpp20-featuresβ concepts, ranges, coroutines,constevalreference/language/constant-expressionsβ fullconstexprevolution across standards