C++ Template Metaprogramming - How to Output the Generated Code

C++ Template Metaprogramming - Is it possible to output the generated code?

No it isn't. The preprocessor has nothing to do with template processing, which is performed by the compiler. Templates do not generate C++ code, any more than a function call does - they are an integral part of the C++ language itself.

Is it possible to output evaluated value using Template Meta Programming during compile time in C++?

Assuming you want to see the value of an integral constant expression, an easy way to see the value for debugging purposes is to create an error with that value. For example:

template <int> struct constexpr_debugger;

int main() {
const int i0(4), i1(17);
constexpr_debugger<i0 + i1> debug;
}

When I compile this code I get the message

constexpr_debugger.cpp: In function ‘int main()’:
constexpr_debugger.cpp:5:37:error: aggregate ‘constexpr_debugger<21> debug’ has incomplete type and cannot be defined
constexpr_debugger<i0 + i1> debug;
^

If you are interested in the values but still want to get the code to compile, the best approach I'm aware of is to create a warning message based, e.g., on an unused variable. Since warnings are never required by the standard, it depends a bit on the compiler and the flags used if warnings are shown. Giving it a test with gcc and clang shows that the compilers unfortunately don't show the value of the template parameter if the above class template is simply defined. The following code shows [currently] that compilers differ in what they report in their diagnostics:

template <int>
void constexpr_debugger()
{
int debug;
}

int main() {
const int i0(4), i1(17);
constexpr_debugger<i0 + i1>();
}

gcc mentions the function including its template parameter and, hence, the desired value when warning about debug not being used. clang warns about debug, too, but doesn't mention the template instantiation where the problem happens. You'll need something of that form which works with the compiler you are using (possibly it needs to be tailored to the compiler version you are using).

C++ metaprogramming automatic function creation?

#include <utility>
#include <tuple>
#include <cstddef>

struct arg
{
template <typename Arg1>
static constexpr decltype(auto) apply(Arg1&& arg1)
{
return std::forward<Arg1>(arg1);
}

static constexpr std::size_t arity = 1;
};

template <typename Type, Type value>
struct constant
{
static constexpr decltype(auto) apply()
{
return value;
}

static constexpr std::size_t arity = 0;
};

template <typename Lhs, typename Rhs>
struct plus
{
template <typename... Args>
static constexpr decltype(auto) apply(Args&&... args)
{
return _apply(std::make_index_sequence<Lhs::arity>{}, std::make_index_sequence<Rhs::arity>{}, std::tuple<Args&&...>(std::forward<Args>(args)...));
}

template <typename Tuple, std::size_t... Arity1, std::size_t... Arity2>
static constexpr decltype(auto) _apply(std::index_sequence<Arity1...>, std::index_sequence<Arity2...>, Tuple&& args)
{
return Lhs::apply(static_cast<typename std::tuple_element<Arity1, Tuple>::type>(std::get<Arity1>(args))...)
+ Rhs::apply(static_cast<typename std::tuple_element<Lhs::arity + Arity2, Tuple>::type>(std::get<Lhs::arity + Arity2>(args))...);
}

static constexpr std::size_t arity = Lhs::arity + Rhs::arity;
};

template <typename Lhs, typename Rhs>
struct multiply
{
template <typename... Args>
static constexpr decltype(auto) apply(Args&&... args)
{
return _apply(std::make_index_sequence<Lhs::arity>{}, std::make_index_sequence<Rhs::arity>{}, std::tuple<Args&&...>(std::forward<Args>(args)...));
}

template <typename Tuple, std::size_t... Arity1, std::size_t... Arity2>
static constexpr decltype(auto) _apply(std::index_sequence<Arity1...>, std::index_sequence<Arity2...>, Tuple&& args)
{
return Lhs::apply(static_cast<typename std::tuple_element<Arity1, Tuple>::type>(std::get<Arity1>(args))...)
* Rhs::apply(static_cast<typename std::tuple_element<Lhs::arity + Arity2, Tuple>::type>(std::get<Lhs::arity + Arity2>(args))...);
}

static constexpr std::size_t arity = Lhs::arity + Rhs::arity;
};

Test:

int main()
{
// (1 + 2) + 3 = 6
std::cout << plus<plus<arg, arg>, arg>::apply(1, 2, 3) << std::endl;

// (a + 5) + (2 * 6) = 9 + 12 = 21
int a = 4;
std::cout << plus<plus<arg, arg>, multiply<arg, constant<int, 6>>>::apply(a, 5, 2) << std::endl;

// ((1 * 2) * 3) * 4 = 24
std::cout << multiply<multiply<multiply<arg, arg>, arg>, arg>::apply(1, 2, 3, 4) << std::endl;

// 2 + (4 * 5) = 22
static_assert(plus<arg, multiply<arg, arg>>::apply(2, 4, 5) == 22, "!");
}

Output:

6
21
24

DEMO 1


The above solution can be improved so that introducing new functors requires less effort, and the declarations themselves are more readable, like below:

#include <iostream>
#include <utility>
#include <tuple>
#include <cstddef>

template <std::size_t Arity>
struct expression
{
static constexpr std::size_t arity = Arity;
};

template <typename Expr, typename Rhs>
struct unary_expression : expression<Rhs::arity>
{
template <typename... Args>
static constexpr decltype(auto) apply(Args&&... args)
{
static_assert(sizeof...(Args) == unary_expression::arity, "Wrong number of operands!");
return Expr::eval(Rhs::apply(std::forward<Args>(args)...));
}
};

template <typename Expr, typename Lhs, typename Rhs>
struct binary_expression : expression<Lhs::arity + Rhs::arity>
{
template <typename... Args>
static constexpr decltype(auto) apply(Args&&... args)
{
static_assert(sizeof...(Args) == binary_expression::arity, "Wrong number of operands!");
return _apply(std::make_index_sequence<Lhs::arity>{}, std::make_index_sequence<Rhs::arity>{}, std::tuple<Args&&...>(std::forward<Args>(args)...));
}

template <typename Tuple, std::size_t... Arity1, std::size_t... Arity2>
static constexpr decltype(auto) _apply(std::index_sequence<Arity1...>, std::index_sequence<Arity2...>, Tuple&& args)
{
return Expr::eval(Lhs::apply(static_cast<typename std::tuple_element<Arity1, Tuple>::type>(std::get<Arity1>(args))...),
Rhs::apply(static_cast<typename std::tuple_element<Lhs::arity + Arity2, Tuple>::type>(std::get<Lhs::arity + Arity2>(args))...));
}
};

struct arg : expression<1>
{
template <typename Arg1>
static constexpr decltype(auto) apply(Arg1&& arg1)
{
return std::forward<Arg1>(arg1);
}
};

template <typename Type, Type value>
struct constant : expression<0>
{
static constexpr decltype(auto) apply()
{
return value;
}
};

template <typename Rhs>
struct negate : unary_expression<negate<Rhs>, Rhs>
{
template <typename Arg1>
static constexpr decltype(auto) eval(Arg1&& arg1)
{
return -std::forward<Arg1>(arg1);
}
};

template <typename Lhs, typename Rhs>
struct plus : binary_expression<plus<Lhs, Rhs>, Lhs, Rhs>
{
template <typename Arg1, typename Arg2>
static constexpr decltype(auto) eval(Arg1&& arg1, Arg2&& arg2)
{
return std::forward<Arg1>(arg1) + std::forward<Arg2>(arg2);
}
};

template <typename Lhs, typename Rhs>
struct minus : binary_expression<minus<Lhs, Rhs>, Lhs, Rhs>
{
template <typename Arg1, typename Arg2>
static constexpr decltype(auto) eval(Arg1&& arg1, Arg2&& arg2)
{
return std::forward<Arg1>(arg1) - std::forward<Arg2>(arg2);
}
};

template <typename Lhs, typename Rhs>
struct multiply : binary_expression<multiply<Lhs, Rhs>, Lhs, Rhs>
{
template <typename Arg1, typename Arg2>
static constexpr decltype(auto) eval(Arg1&& arg1, Arg2&& arg2)
{
return std::forward<Arg1>(arg1) * std::forward<Arg2>(arg2);
}
};

int main()
{
// (1 + 2) + 3 = 6
std::cout << plus<plus<arg, arg>, arg>::apply(1, 2, 3) << std::endl;

// ((a + 5) + (2 * 6)) - 5 = 16
int a = 4;
std::cout << minus<plus<plus<arg, arg>, multiply<arg, constant<int, 6>>>, constant<int, 5>>::apply(a, 5, 2) << std::endl;

// ((1 * 2) * 3) * 4 = 24
std::cout << multiply<multiply<multiply<arg, arg>, arg>, arg>::apply(1, 2, 3, 4) << std::endl;

// -((3 * 4) + (5 - 6)) = -11
static_assert(negate<plus<multiply<arg, arg>, minus<arg, arg>>>::apply(3, 4, 5, 6) == -11, "!");
}

DEMO 2

C++ Template Metaprogramming- not sure I quite get the fuss?

Have you read the zillions of articles out there that thoroughly discuss TM? For example:

  • http://www.codeproject.com/Articles/3743/A-gentle-introduction-to-Template-Metaprogramming
  • http://aszt.inf.elte.hu/~gsd/halado_cpp/ch06s04.html
  • http://blog.lorentey.hu/2010/04/21/cpp-template-metaprogramming/
  • etc, etc.

A simple way of looking at (one kind) of template metaprogramming is as "strong" memoization. A common example is the following:

Lets say your program needs the compute a factorial for some number. With TM you can compute the factorial in compile-time, which increases compile time (and binary size) but decreases runtime. The example code is from the 2nd site above; if you were doing it the "naive" way, you'd have code that looks like:

int factorial( int n) {
return (n==0) ? 1 : n*factorial(n-1)l
}

int main() {
cout << factorial(5) << endl;
return 0;
}

With TM we can compute the factorial at compile-time:

// factorial.cpp

#include <iostream>

template <int N>
struct Factorial {
enum { value = N * Factorial<N-1>::value };
};

template <>
struct Factorial<1> {
enum { value = 1 };
};

// example use
int main() {
const int fact5 = Factorial<15>::value;
std::cout << fact5 << endl;
return 0;
}

Here Factorial<15>::value is essentially a compile-time constant. As always, simplistic examples aren't particularly useful, but hopefully you get the gist of it.

C++ template metaprogramming for argument dispatch

So the first part, (minus useless part)

// list of type
template<class... T>
struct mp_list {
};

// list of type, which are integral_constant
// so we can see it like a list of constexpr T
// since we can use ::value on each type
template<class T, T... I> using mp_list_c = mp_list<std::integral_constant<T, I>...>;

template<class... Ts, class F>
constexpr void mp_for_each_impl(mp_list<Ts...>, F &&f) {
// old school
(void) (std::initializer_list<int>{(f(Ts()), 0)...});
//C++17 :
// (f(Ts()),...); // see : https://en.cppreference.com/w/cpp/language/fold
// it call f(x) for each x in Ts
}

template<class L, class F>
constexpr void mp_for_each(F &&f) {
// mp_for_each_impl is needed to get the template list out of "L"
mp_for_each_impl(L{}, std::forward<F>(f));
}

Note about "the useless part" :

template<class... Ts, class F>
constexpr F mp_for_each_impl(mp_list<Ts...>, F &&f) {
return (void) (std::initializer_list<int>{(f(Ts()), 0)...}), std::forward<F>(f);
}

Since for_each doesn't need to return anything, it can return the function, may be it can be usefull in some case. And it mimics https://en.cppreference.com/w/cpp/algorithm/for_each

template<class A, template<class...> class B>
struct mp_rename_impl {
};

template<template<class...> class A, class... T,
template<class...> class B>
struct mp_rename_impl<A<T...>, B> {
using type = B<T...>;
};

template<class A, template<class...> class B> using mp_rename = typename mp_rename_impl<A, B>::type;

AFAIU, mp_rename is use to be generic, since mp_for_each_impl use mp_list the rename is used to convert "any type with a variadic template" into mp_list.

Then in :

template<int ... Is, typename F>
void dispatch_int(int idx, F &&f) {
if (!dispatch_int_noexcept<Is...>(idx, std::forward<F>(f))) {
std::stringstream ss;
mp_for_each<mp_list_c<int, Is...>>([=, &ss](auto I) { ss << decltype(I)::value << " "; });
std::cout << "unknown value " << idx << ", available: " << ss.str() << std::endl;
}
}

the dispatch_int_noexcept<Is...>(idx, std::forward<F>(f)) do the work and the rest is for the error message.

then :

template<int... Is, typename F>
bool dispatch_int_noexcept(int idx, F &&f) {
// just a check
static_assert(sizeof...(Is) > 0, "you need to provide at least one candidate");
bool notFound = true;
mp_for_each<mp_list_c<int, Is...>>([=, ¬Found, &f, &idx](auto I) {
// look if the 'ndim' match one of the ints of dispatch_int<2, 3, 4>
// note they are now std::integral_constant, so ::value
if (decltype(I)::value == idx && notFound) {
// found : call the early lambda with the integral_constant
// that why you do the 'constexpr int NDim = decltype(I)::value;'
std::forward<F>(f)(I);
notFound = false;
}
});
return !notFound;
}

Tell me if it helps

print a list of numbers using template metaprogramming

Functions must be declared before they can be called. Change the order of functions.

Your special case is not a specialization of the base case, so the base case cannot see it. Because the template argument list is different, it is simply an overloaded function.

template <typename T>
void foo(T i){
std::cout << i << std::endl;
std::cout << "end of list" << std::endl;
}

template <typename T, typename ...Args>
void foo(T i, Args ... args){
std::cout << i << std::endl;
foo(args...);
}

C++ metaprogramming - generating errors in code

If you don't want Boost C++ Libraries magic and want bare bones...

template<bool> class static_check
{
};

template<> class static_check<false>
{
private: static_check();
};

#define StaticAssert(test) static_check<(test) != 0>()

Then use StaticAssert. It's a #define for me because I have code that needs to run in a lot of environments where C++ doesn't work right for templates and I need to just back it off to a runtime assert. :(

Also, not the best error messages.

C++ Template preprocessor tool

Comeau C++ can "compile" C++ to C. This would seem to be close to your goal, as OpenCL does not support C++ – it's much closer to C.

Any metaprogramming way to generate overloads for various numbers of template parameters?

You can try and do the same thing as Boost does, for example in Boost.Function (link to the template header). They use Boost.Preprocessor to enumerate various things on the given number of arguments. For example consider you want to overload a function for 0-2 arguments. The conventional way would be the following:

void foo(){...}
template<class T0>
void foo(T0 a0){...}
template<class T0, class T1>
void foo(T0 a0, T1 a1){...}

Now what Boost does, is to just put those template parameters (class T0 etc) into a preprocessor macro, use that inside the function, and then include the header 3 times for different number of arguments. Example:

// template header, no include guard
#define FOO_TEMPLATE_PARAMS BOOST_PP_ENUM_PARAMS(FOO_NUM_ARGS,class T)
#define FOO_PARAM(J,I,D) BOOST_PP_CAT(T,I) BOOST_PP_CAT(a,I)
#define FOO_PARAMS BOOST_PP_ENUM(FOO_NUM_ARGS,FOO_PARAM,BOOST_PP_EMTPY)
#if FOO_NUM_ARGS > 0
#define FOO_TEMPLATE template< FOO_TEMPLATE_PARAMS >
#else
#define FOO_TEMPLATE
#endif

FOO_TEMPLATE
void foo(FOO_PARAMS){...}

// cleanup what we've done
#undef FOO_TEMPLATE_PARAM
#undef FOO_TEMPLATE_PARAMS
#undef FOO_PARAM
#undef FOO_PARAMS
#undef FOO_TEMPLATE

The above is the template header, lets call it Foo_Template.h. Now we just include it for the number of arguments we want:

// Foo.h
#include <boost/preprocessor.hpp>
#define FOO_NUM_ARGS 0
#include "Foo_Template.h"
#define FOO_NUM_ARGS 1
#include "Foo_Template.h"
#define FOO_NUM_ARGS 2
#include "Foo_Template.h"
#define FOO_NUM_ARGS 3
#include "Foo_Template.h"
#define FOO_NUM_ARGS 4
#include "Foo_Template.h"
#undef FOO_NUM_ARGS

Perfect! With a little more preprocessor effort and boilerplate "code", we can now overload foo for any number of arguments! The preprocessor macros will expand to something like this:

// with FOO_NUM_ARGS == 0
#define FOO_TEMPLATE_PARAMS /*empty, because we enumerate from [0,FOO_NUM_ARGS)*/
#define FOO_PARAMS /*empty again*/
#define FOO_TEMPLATE /*empty, we got the 0 args version*/

void foo(){...}

// with FOO_NUM_ARGS == 1
#define FOO_TEMPLAtE_PARAMS class T0 /* BOOST_PP_ENUM is like a little for loop */
#define FOO_PARAMS T0 a0
#define FOO_TEMPLATE template< class T0 >

template< class T0 >
void foo( T0 a0 ){...}

// with FOO_NUM_ARGS == 3
#define FOO_TEMPLAtE_PARAMS class T0, class T1, class T2
#define FOO_PARAMS T0 a0, T1 a1, T2 a2
#define FOO_TEMPLATE template< class T0, class T1, class T2 >

template< class T0, class T1, class T2 >
void foo( T0 a0, T1 a1, T2 a2 ){...}

But it's a bliss we won't need this anymore with C++0x thanks to variadic templates. I love 'em.



Related Topics



Leave a reply



Submit