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: 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.
template<typename T, typename = void>
struct is_valid : std::false_type {};
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 {};
std::declval<T>()
creates an instance of type T in an unevaluated context.