c++
Source files of most examples is available here.
Type deduction
Force compile error to see what auto
is deduced to.
auto foo = bar();
// force compile error
typename decltype(foo)::_;
Variadic templates (parameter pack)
#include <iostream>
// -- Example 1 - print template value arguments.
// Base case with one parameter.
template<int P>
void show_int() {
printf("%d\n", P);
}
// General case with at least two parameters, to disambiguate from base case.
template<int P0, int P1, int... Params>
void show_int() {
printf("%d, ", P0);
show_int<P1, Params...>();
}
// -- Example 2 - print values of different types.
// Base case with one parameter.
template<typename T>
void show(const T& t) {
std::cout << t << '\n';
}
// General case with at least two parameters, to disambiguate from base case.
template<typename T0, typename T1, typename... Types>
void show(const T0& t0, const T1& t1, const Types&... types) {
std::cout << t0 << ", ";
show(t1, types...);
}
int main() {
show_int<1, 2, 3, 4, 5>();
show(1, 1.0, "foo", 'a');
}
Forwarding reference (fwd ref)
A forwarding reference
is a special references that preserves the value category
of a function parameter and therefore allows for perfect
forwarding.
A forwarding reference is a parameter of a function template, which is declared
as rvalue
reference to a non-cv
qualified type
template parameter.
template<typename T>
void fn(T&& param); // param is a forwarding reference
Perfect forwarding can be achieved with std::forward
. This for
example allows a wrapper function to pass a parameter with the exact same
value category to a down-stream function which is being invoked in the wrapper.
#include <cstdio>
#include <utility>
struct M {};
// -- CONSUMER -----------------------------------------------------------------
void use(M&) {
puts(__PRETTY_FUNCTION__);
}
void use(M&&) {
puts(__PRETTY_FUNCTION__);
}
// -- TESTER -------------------------------------------------------------------
template<typename T>
void wrapper(T&& param) { // forwarding reference
puts(__PRETTY_FUNCTION__);
// PARAM is an lvalue, therefore this always calls use(M&).
use(param);
}
template<typename T>
void fwd_wrapper(T&& param) { // forwarding reference
puts(__PRETTY_FUNCTION__);
// PARAM is an lvalue, but std::forward returns PARAM with the same value
// category as the forwarding reference takes.
use(std::forward<T>(param));
}
// -- MAIN ---------------------------------------------------------------------
int main() {
{
std::puts("==> wrapper rvalue reference");
wrapper(M{});
// calls use(M&).
std::puts("==> wrapper lvalue reference");
struct M m;
wrapper(m);
// calls use(M&).
}
{
std::puts("==> fwd_wrapper rvalue reference");
fwd_wrapper(M{});
// calls use(M&&).
std::puts("==> fwd_wrapper lvalue reference");
struct M m;
fwd_wrapper(m);
// calls use(M&).
}
}
Example: any_of
template meta function
#include <type_traits>
template<typename T, typename... U>
struct any_of : std::false_type {};
// Found our type T in the list of types U.
template<typename T, typename... U>
struct any_of<T, T, U...> : std::true_type {};
// Pop off the first element in the list of types U,
// since it didn't match our type T.
template<typename T, typename U0, typename... U>
struct any_of<T, U0, U...> : any_of<T, U...> {};
// Convenience template variable to invoke meta function.
template<typename T, typename... U>
constexpr bool any_of_v = any_of<T, U...>::value;
static_assert(any_of_v<int, char, bool, int>, "");
static_assert(!any_of_v<int, char, bool, float>, "");
Example: SFINAE (enable_if)
Provide a single entry point Invoke
to call some Operations
.
Use enable_if
to enable/disable the template functions depending on the two
available traits an operation can have:
- Operation returns a result
- Operation requires a context
#include <iostream>
#include <type_traits>
// Helper meta fns.
template<typename T>
using enable_if_bool = std::enable_if_t<T::value, bool>;
template<typename T>
using disable_if_bool = std::enable_if_t<!T::value, bool>;
template<typename T>
using has_dst = std::integral_constant<bool, !std::is_same<typename T::Return, void>::value>;
// Template meta programming invoke machinery.
namespace impl {
// Invoke an OPERATION which *USES* a context.
template<typename Ctx, template<typename> class Op, typename... P,
enable_if_bool<typename Op<Ctx>::HasCtx> = true>
typename Op<Ctx>::Return Invoke(const Ctx& C, P... params) {
return Op<Ctx>()(C, params...);
}
// Invoke an OPERATION which uses *NO* context.
template<typename Ctx, template<typename> class Op, typename... P,
disable_if_bool<typename Op<Ctx>::HasCtx> = true>
typename Op<Ctx>::Return Invoke(const Ctx&, P... params) {
return Op<Ctx>()(params...);
}
} // namespace impl
// Invoke an OPERATION which *HAS* a DESTINATION with arbitrary number of arguments.
template<typename Ctx, template<typename> class Op, typename... P,
enable_if_bool<has_dst<Op<Ctx>>> = true>
void Invoke(const Ctx& C, P... params) {
std::cout << "Invoke " << Op<Ctx>::Name << '\n';
typename Op<Ctx>::Return R = impl::Invoke<Ctx, Op>(C, params...);
std::cout << "returned -> " << R << '\n';
}
// Invoke an OPERATION which has *NOT* a DESTINATION with arbitrary number of arguments.
template<typename Ctx, template<typename> class Op, typename... P,
disable_if_bool<has_dst<Op<Ctx>>> = true>
void Invoke(const Ctx& C, P... params) {
std::cout << "Invoke " << Op<Ctx>::Name << " without destination." << '\n';
impl::Invoke<Ctx, Op>(C, params...);
}
// Custom context.
struct Ctx {
void out(const char* s, unsigned v) const {
printf("%s%x\n", s, v);
}
};
// Operations to invoke.
template<typename Ctx>
struct OpA {
using HasCtx = std::false_type;
using Return = int;
static constexpr const char* const Name = "OpA";
constexpr Return operator()(int a, int b) const {
return a + b;
}
};
template<typename Ctx>
struct OpB {
using HasCtx = std::true_type;
using Return = void;
static constexpr const char* const Name = "OpB";
Return operator()(const Ctx& C, unsigned a) const {
C.out("a = ", a);
}
};
int main() {
Ctx C;
Invoke<Ctx, OpA>(C, 1, 2);
Invoke<Ctx, OpB>(C, 0xf00du);
return 0;
}
Example: Minimal templatized test registry
A small test function registry bringing together a few different template features.
#include <cstdio>
#include <functional>
#include <map>
#include <string>
#include <type_traits>
template<typename R, typename... P>
struct registry {
using FUNC = R (*)(P...);
using SELF = registry<R, P...>;
using RET = R;
static SELF& get() {
static SELF r;
return r;
}
bool add(std::string nm, FUNC fn) {
const auto r = m_fns.insert({std::move(nm), std::move(fn)});
return r.second;
}
R invoke(const std::string& nm, P... p) const {
return invoke_impl<R>(nm, p...);
}
void dump() const {
for (const auto& it : m_fns) {
std::puts(it.first.c_str());
}
}
private:
std::map<std::string, FUNC> m_fns;
template<typename RET>
std::enable_if_t<std::is_same_v<RET, void>> invoke_impl(const std::string& nm, P... p) const {
const auto it = m_fns.find(nm);
if (it == m_fns.end()) {
return;
}
std::invoke(it->second, p...);
}
template<typename RET>
std::enable_if_t<!std::is_same_v<RET, void>, RET> invoke_impl(const std::string& nm,
P... p) const {
const auto it = m_fns.find(nm);
if (it == m_fns.end()) {
static_assert(std::is_default_constructible_v<RET>,
"RET must be default constructible");
return {};
}
return std::invoke(it->second, p...);
}
};
#define TEST_REGISTER(REGISTRY, NAME) \
static bool regfn_##REGISTRY##NAME() { \
const bool r = REGISTRY::get().add(#NAME, NAME); \
if (!r) { \
std::puts("Failed to register test " #NAME ", same name already registered!"); \
std::abort(); \
} \
return r; \
} \
static const bool reg_##REGISTRY##NAME = regfn_##REGISTRY##NAME();
#define TEST(REGISTRY, NAME, ...) \
REGISTRY::RET NAME(__VA_ARGS__); \
TEST_REGISTER(REGISTRY, NAME); \
REGISTRY::RET NAME(__VA_ARGS__)
// -- Usage 1 simple usage.
using REG1 = registry<void>;
TEST(REG1, test1) {
std::puts("REG1::test1");
}
TEST(REG1, test2) {
std::puts("REG1::test2");
}
// -- Usage 2 with convenience macro wrapper.
using REG2 = registry<void, bool>;
#define TEST2(NAME, ...) TEST(REG2, NAME, ##__VA_ARGS__)
TEST2(test1, bool val) {
printf("REG2::test1 val %d\n", val);
}
int main() {
const auto& R1 = REG1::get();
R1.dump();
R1.invoke("test1");
R1.invoke("test2");
const auto& R2 = REG2::get();
R2.dump();
R2.invoke("test1", true);
return 0;
}
Example: Concepts pre c++20
Prior to c++20's concepts, SFINAE
and std::void_t
can be leveraged to build
something similar allowing to define an interface (aka trait) for a template
parameter.
template<typename T, template<typename> class Checker, typename = void>
struct is_valid : std::false_type {};
template<typename T, template<typename> class Checker>
struct is_valid<T, Checker, std::void_t<Checker<T>>> : std::true_type {};
template<typename T, template<typename> class Checker>
static constexpr bool is_valid_v = is_valid<T, Checker>::value;
// -----------------------------------------------------------------------------
template<typename T, typename R, template<typename> class Checker, typename = void>
struct is_valid_with_ret : std::false_type {};
template<typename T, typename R, template<typename> class Checker>
struct is_valid_with_ret<T, R, Checker, std::void_t<Checker<T>>> : std::is_same<R, Checker<T>> {};
template<typename T, typename R, template<typename> class Checker>
static constexpr bool is_valid_with_ret_v = is_valid_with_ret<T, R, Checker>::value;
// -----------------------------------------------------------------------------
template<typename T>
struct is_entry {
template<typename TT>
using init = decltype(std::declval<TT>().init());
template<typename TT>
using tag = decltype(std::declval<TT>().tag());
template<typename TT>
using val = decltype(std::declval<TT>().val());
static constexpr bool value = is_valid_v<T, init> && is_valid_with_ret_v<T, int, tag> &&
is_valid_with_ret_v<T, typename T::Type, val>;
};
template<typename T>
static constexpr bool is_entry_v = is_entry<T>::value;
template<typename E>
struct Entry {
using Type = E;
void init();
int tag() const;
E val() const;
};
int main() {
static_assert(is_entry_v<Entry<bool>>, "");
}
The main mechanic can be explained with the following reduced example. If one
of the decltype(std:declval<T>...
expressions is ill-formed, the template
specialization for is_valid
will be removed from the candidate set due to
SFINAE.
#include <type_traits>
// (1) Primary template.
template<typename T, typename = void>
struct is_valid : std::false_type {};
// (2) Partial template specialization.
template<typename T>
struct is_valid<T, std::void_t<decltype(std::declval<T>().some_fun1()),
decltype(std::declval<T>().some_fun2())>> : std::true_type {};
struct A {
void some_fun1() {}
void some_fun2() {}
};
struct B {};
static_assert(is_valid<A>::value, "is true");
// * Compare template arg list with primary template, we only supplied one
// arg, the second one will be defaulted as
// is_valid<A, void>
// * Compare template arg list against available specializations, this will
// try to match the pattern <A, void> against the patterns defined in the
// partial specializations.
// * Try specialization (2)
// * T -> A
// * Evaluate std::void_t -> decltype's are well-formed
// std::void_t<...> -> void
// * Specialization (2) matches <A, void>
// * Pick the most specialized version -> (2)
static_assert(!is_valid<B>::value, "is false");
// * Compare template arg list with primary template, we only supplied one
// arg, the second one will be defaulted as
// is_valid<A, void>
// * Compare template arg list against available specializations, this will
// try to match the pattern <B, void> against the patterns defined in the
// partial specializations.
// * Try specialization (2)
// * T -> B
// * Evaluate std::void_t -> decltype's are ill-formed
// * Specialization (2) is removed from candidate set, no hard error (SFINAE)
// * No specialization matches, take the primary template.
std::declval<T>()
creates an instance of type T in an unevaluated context.
A more detailed description is available in the SO discussion How does
void_t
work.
Template selection with partially / fully specializations.
enum Kind {
kPrimary,
kTT,
kIntBool,
kIntInt,
};
// (1) Primary template.
template<typename T, typename U = bool>
struct pair {
static constexpr Kind kind = kPrimary;
};
// (2) Partial template specialization.
template<typename T>
struct pair<T, T> {
static constexpr Kind kind = kTT;
};
// (3) Template specialization.
template<>
struct pair<int, bool> {
static constexpr Kind kind = kIntBool;
};
// (4) Template specialization.
template<>
struct pair<int, int> {
static constexpr Kind kind = kIntInt;
};
int main() {
static_assert(pair<int>::kind == kIntBool, "");
// * Compare template arg list with primary template, we only supplied one
// arg, the second one will be defaulted as
// pair<int, bool>
// * Compare template arg list against available specializations, this will
// try to match the pattern <int, bool> against the patterns defined in the
// partial specializations.
// * (2) <int, bool> pattern does not match
// * (3) <int, bool> pattern does match
// * (4) <int, bool> pattern does not match
// * Pick the most specialized version -> (3)
static_assert(pair<char, char>::kind == kTT, "");
// * Compare template arg list against available specializations, this will
// try to match the pattern <char, char> against the patterns defined in the
// partial specializations.
// * (2) <char, char> pattern does match
// * (3) <char, char> pattern does not match
// * (4) <char, char> pattern does not match
// * Pick the most specialized version -> (2)
static_assert(pair<int, int>::kind == kIntInt, "");
// * Compare template arg list against available specializations, this will
// try to match the pattern <int, int> against the patterns defined in the
// partial specializations.
// * (2) <int, int> pattern does match
// * (3) <int, int> pattern does match
// * (4) <int, int> pattern does not match
// * Pick the most specialized version -> (3)
static_assert(pair<char, short>::kind == kPrimary, "");
// * Compare template arg list against available specializations, this will
// try to match the pattern <char, short> against the patterns defined in the
// partial specializations.
// * (2) <char, short> pattern does not match
// * (3) <char, short> pattern does not match
// * (4) <char, short> pattern does not match
// * No specialization matches, take the primary template.
}
Example: Perfect forwarding
#include <cassert>
#include <cstdio>
#include <new>
#include <type_traits>
#include <utility>
struct S {};
struct M {
M() {
std::puts("M()");
}
M(const M&) {
std::puts("M(M&)");
}
M(M&&) {
std::puts("M(M&&)");
}
M& operator=(const M&) = delete;
M& operator=(M&&) = delete;
M(S&, int) {
std::puts("M(S&)");
}
M(S&&, int) {
std::puts("M(S&&)");
}
~M() {
std::puts("~M()");
}
};
template<typename T>
struct option {
static_assert(!std::is_reference_v<T>);
constexpr option() = default;
template<typename... Params>
constexpr option(Params&&... params) : m_has_val(true) {
// BAD: does not perfectly forward!
// eg, if option(S&&) is invoked, this would invoke M(S&).
// new (&m_val) T(params...);
// GOOD: perfectly forwards params to constructor of T.
new (m_val) T(std::forward<Params>(params)...);
}
~option() {
reset();
}
constexpr T& value() {
assert(m_has_val);
return *reinterpret_cast<T*>(m_val);
}
private:
constexpr void reset() {
if (!m_has_val) {
return;
}
if constexpr (!std::is_trivially_destructible_v<T>) {
value().~T();
};
}
alignas(T) char m_val[sizeof(T)];
bool m_has_val{false};
};
int main() {
std::puts("==> case 1");
// invokes M(S&&, int)
option<M> opt1(S{}, 123);
std::puts("==> case 2");
// invokes M() + M(M&&)
option<M> x /* option(M&&) + M(M&&) */ = M{} /* M() */;
}