Можно ли выводить переменные constexpr во время компиляции для отладки метапрограмм шаблонов?

Я отлаживаю метафункцию, которая выполняет итерацию по вариативному параметру шаблона и проверяет пары (Type, Tag), чтобы увидеть, помечен ли каждый Type соответствующим 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;
}

Что-то не так с моим кодом, и я хочу его отладить.

Если я использую static_assert для вывода restN или любой другой переменной constexpr, моя программа break во время компиляции в точке утверждения с выводом, который я прописываю. Также мне пока непонятно, как записать что-то кроме строкового литерала с static_assert().

Как я могу заставить метапрограмму перебирать вариативный параметр шаблона и выводить данные, которые мне нужны для отладки?

Полный пример:

#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>()); 
};

person tmaric    schedule 09.01.2019    source источник
comment
Я не читал это внимательно, поэтому это не ответ, но эта страница обсуждает Техника Эрвина Унру для генерации сообщений об ошибках с простыми числами во время компиляции. На странице также обсуждается то, что претендует на то, чтобы быть обновлением C++ 17 для этой техники.   -  person Pete Becker    schedule 10.01.2019


Ответы (3)


Это, вероятно, больше усилий, чем вы надеялись, но есть также патч, который вы можете обратитесь к GCC, чтобы включить оператор static_print, который делает то, что вы ищете.

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

Компиляция вышеуказанной программы выводит (во время компиляции): The template test has been instantiated as test<int, 3>. By the way, s + 1 is 4

person 0x5453    schedule 09.01.2019
comment
Усилия могут окупиться, если они ускорят отладку, но стоит ли мне как-то беспокоиться об исправлении GCC? - person tmaric; 10.01.2019
comment
@tmaric вы бы использовали неисправленный gcc для сборок без отладки. Применяйте регулярные средства защиты ненадежного программного обеспечения по своему усмотрению (например, запускайте одноразовую виртуальную машину). - person Caleth; 10.01.2019

Что касается отображения типа при компиляции для отладки, вы можете создать экземпляр неполного типа, используя значение:

template <int> struct debug_int;

а потом:

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

Простая демонстрация
Демонстрация вашего случая

Кстати, ваш метод taggedTypes можно упростить до:

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

Демо

person Jarod42    schedule 09.01.2019
comment
Круто, но это дает ошибку компиляции, которая на этом останавливается. Мне интересно, есть ли что-то вроде печати во время компиляции, которую я могу использовать для отладки. - person tmaric; 10.01.2019
comment
@tmaric: не из стандарта, 0x5453 предоставляет ссылку на патч для gcc, чтобы сделать это. - person Jarod42; 10.01.2019

Я немного покопался и нашел, вероятно, уродливое решение, которое не останавливает компиляцию и не требует патча. Я использую метафункцию, которая вызывает предупреждение компилятора. Например, в gcc можно использовать -Wbool-compare для вывода результатов вычислений во время компиляции:

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

Это дает (с grep в 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]’:
person tmaric    schedule 10.01.2019