c++

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');
}

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.
}