tmaric
tmaric

Reputation: 5477

Is it possible to output constexpr variables at compile time to debug template metaprograms?

I am debugging a metafunction that iterates over a variadic template parameter and checks pairs (Type, Tag) to see if each Type is tagged with the corresponding Tag:

template<typename Type, typename Tag, typename ... Rest> 
constexpr bool taggedTypes()
{
    constexpr std::size_t restN = sizeof ...(Rest);  
    static_assert(restN % 2 == 0, "Odd number of (Type, Tag) pairs.");

    constexpr bool pairDoesntMatch = ! taggedType<Type, Tag>(); 
    if constexpr (pairDoesntMatch)
        return false; 

    // Single pair, empty Rest, pair matches. 
    if (restN == 0)
        return true; 

    // More than two pairs, test further.
    if (restN > 2)
        taggedTypes<Rest...>(); 

    return true;
}

Something is wrong with my code, and I want to debug it.

If I use static_assert to output restN or any other constexpr variable, my program will break at compile time at the point of assertion with an output I prescribe. Also, it is not clear to me yet how to write down anything apart from a string literal with static_assert().

How can I make the metaprogram iterate over the variadic template parameter and output stuff that I need for debugging?

The complete example:

#include <cassert> 
#include <type_traits>
#include <cstddef>

struct fruit_tag {}; 
struct veggie_tag {}; 


template<typename T>
struct tag;

template<typename T, typename Tag>  
constexpr 
bool 
taggedType()
{
    constexpr bool sameTypes 
        = std::is_same<typename tag<T>::type, Tag>(); 

    static_assert(sameTypes); 

    return sameTypes; 
}

template<typename Type, typename Tag, typename ... Rest> 
constexpr bool taggedTypes()
{
    constexpr std::size_t restN = sizeof ...(Rest);  
    static_assert(restN % 2 == 0, "Odd number of (Type, Tag) pairs.");

    constexpr bool pairDoesntMatch = ! taggedType<Type, Tag>(); 
    if constexpr (pairDoesntMatch)
        return false; 

    // Single pair, empty Rest, pair matches. 
    if (restN == 0)
        return true; 

    // Many pairs, test further.
    if (restN > 2)
        taggedTypes<Rest...>(); 

    return true;
}

class Orange {}; 

template<>
struct tag<Orange>
{
    using type = fruit_tag;  
};

class Apple {}; 

template<>
struct tag<Apple>
{
    using type = fruit_tag;  
};

class Turnip{}; 

template<>
struct tag<Turnip>
{
    using type = veggie_tag; 
};

int main()
{
    static_assert(taggedTypes<Turnip, veggie_tag, Orange, fruit_tag>()); 
};

Upvotes: 3

Views: 1658

Answers (3)

tmaric
tmaric

Reputation: 5477

I dug around a bit and found a probably ugly solution that doesn't stop the compilation and doesn't require a patch. I am using a metafunction that causes a compiler warning to flag. For example, with gcc, -Wbool-compare can be used like this, to output results of compile-time calculations:

template<int N> 
constexpr bool warning_print()
{
    return (0 < N < 100); 
}

template<int N, int M> 
constexpr void iterate()
{
    warning_print<N>(); 

    if constexpr (N + 1 < M)
        iterate<N+1, M>(); 

    return; 
}

using namespace std; 

int main()
{
    iterate<5, 10>(); 
}

This gives (with grep on Linux):

$ mainmake 2>&1 | grep -Ev 'recursive|required|comparisons like|(0 < N < 100)' 
main.cpp: In function ‘constexpr bool warning_print()’:
main.cpp:4:19: warning: comparison of constant ‘100’ with boolean expression is always true [-Wbool-compare]
             ~~~~~~^~~~~
main.cpp: In instantiation of ‘constexpr bool warning_print() [with int N = 5]’:
             ~~^~~
main.cpp: In instantiation of ‘constexpr bool warning_print() [with int N = 6]’:
main.cpp: In instantiation of ‘constexpr bool warning_print() [with int N = 7]’:
main.cpp: In instantiation of ‘constexpr bool warning_print() [with int N = 8]’:
main.cpp: In instantiation of ‘constexpr bool warning_print() [with int N = 9]’:

Upvotes: 1

Jarod42
Jarod42

Reputation: 217293

As for displaying type at compile type for debugging, you might instantiate a non complete type using the value:

template <int> struct debug_int;

and then:

constexpr int magic = 42;
debug_int<magic>{}; // Compile error: invalid use of incomplete type 'struct debug_int<42>'

Simple Demo
Demo on your case

BTW, your taggedTypes method can be simplified to:

template <typename Tuple, std::size_t ... Is> 
constexpr bool taggedTypes(std::index_sequence<Is...>)
{
    return (std::is_same<typename tag<std::tuple_element_t<2 * Is, Tuple>>::type,
                         std::tuple_element_t<2 * Is + 1, Tuple>>::value && ...);   
}

template <typename ... Ts> 
constexpr bool taggedTypes()
{
    constexpr std::size_t size = sizeof ...(Ts);
    //[[maybe_unused]]debug_odd<size> debug{};
    static_assert(size % 2 == 0, "Odd number of (Type, Tag) pairs.");

    return taggedTypes<std::tuple<Ts...>>(std::make_index_sequence<size / 2>{});
}

Demo

Upvotes: 2

0x5453
0x5453

Reputation: 13589

This is probably more effort than you were hoping for, but there is also a patch that you can apply to GCC to enable a static_print statement that does what you are looking for.

template<typename T, int s>
struct test
{
    static_print("The template ", ::test, " has been instantiated as ", test, ". By the way, s + 1 is ", s + 1);
};

int main() {
    test<int, 3> y;
    return 0;
}

Compiling the above program prints out (at compile time): The template test has been instantiated as test<int, 3>. By the way, s + 1 is 4

Upvotes: 1

Related Questions