在编译时打印模板类型名

问题描述 投票:0回答:8

在 C++ 中创建模板函数时,是否有一种简单的方法可以将模板的类型名表示为字符串?我有一个简单的测试用例来展示我正在尝试做什么(请注意,显示的代码无法编译):

#include <stdio.h>
template <typename type>
type print(type *addr) 
{
  printf("type is: %s",type);
}

int main()
{
  int a;
  print(&a);
}

// Would like to print something like:
// type is: int

我认为类型名应该在实例化函数时在编译时可用,但我对模板不太熟悉,而且我还没有看到一种将类型名作为字符串获取的方法。

我想这样做的原因是为了一些 printf 类型的调试。我有多个线程正在运行,并且使用 gdb 单步执行会改变程序行为。因此,对于某些事情,我想转储有关正在执行的函数的信息。这并不是太重要,因此如果解决方案过于复杂,我会跳过将此信息添加到我的日志记录功能中。但如果有一种简单的方法可以做到这一点,那么这将是有用的信息。

c++ g++
8个回答
97
投票

获取有用的编译时名称:

假设您有一些名为“T”的未知类型。你可以通过可怕的使用它让编译器打印它的类型。例如:

typedef typename T::something_made_up X;

错误消息将类似于:

error: no type named 'something_made_up' in 'Wt::Dbo::ptr<trader::model::Candle>'

“in”后面的位显示类型。 (仅使用 clang 进行测试)。

其他触发方式:

bool x = T::nothing;   // error: no member named 'nothing' in 'Wt::Dbo::ptr<trader::model::Candle>'
using X = typename T::nothing;  // error: no type named 'nothing' in 'Wt::Dbo::ptr<trader::model::Candle>'

使用 C++11,您可能已经有一个对象并使用“decltype”来获取其类型,因此您也可以运行:

auto obj = creatSomeObject();
bool x = decltype(obj)::nothing; // (Where nothing is not a real member). 

23
投票

__PRETTY_FUNCTION__
应该可以解决您的问题(至少在运行时)

下面程序的输出是:

asfdasdfasdf test<type>::test() [with type = int]
asfdasdfasdf test<type>::test() [with type = int]
asfdasdfasdf test<type>::test() [with type = int]
asfdasdfasdf test<type>::test() [with type = int]
asfdasdfasdf test<type>::test() [with type = int]
asfdasdfasdf test<type>::test() [with type = int]
asfdasdfasdf void tempFunction() [with type = bool]
asfdasdfasdf void tempFunction() [with type = bool]
asfdasdfasdf void tempFunction() [with type = bool]
asfdasdfasdf void tempFunction() [with type = bool]
asfdasdfasdf void tempFunction() [with type = bool]
asfdasdfasdf void tempFunction() [with type = bool]
!!!Hello World!!!

如果您真的非常需要将类型名称作为字符串,您可以破解它(使用

snprintf
而不是
printf
)并在 '=' 之后和 ']' 之前拉出子字符串。

#include <iostream>
using namespace std;

template<typename type>
class test
{
public:
test()
{
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
}
};

template<typename type>
void tempFunction()
{
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
    printf("asfdasdfasdf %s\n", __PRETTY_FUNCTION__);
}

int main() {
    test<int> test;

    tempFunction<bool>();
    cout << "!!!Hello World!!!" << endl; // prints !!!Hello World!!!
    return 0;
}

13
投票

另一种编译时解决方案,类似于 matiu 提供的解决方案,但也许更具描述性的是使用包装在一个小辅助函数中的

static_assert

#include <utility>

template <typename T>
struct always_false : std::false_type { };

template <typename T>
constexpr bool always_false_v = always_false<T>::value;

template <typename T>
void print_type_in_compilation_error(T&&)
{
    static_assert(always_false_v<T>,
                  "Compilation failed because you wanted to know the type; see below:");
}
// usage:
int I;
print_type_in_compilation_error(I);

上面会给你一个很好的错误消息(在 MSVC 和 Clang 中测试),就像其他答案一样,但在我看来代码更容易理解。

注意原始答案中的代码格式不正确,因此我对代码进行了一些更改。

always_false
的功劳归于 this 答案。


10
投票

既然您说过您需要它来进行调试,也许运行时解决方案也是可以接受的。并且您已将其标记为 g++,因此您不想遵守标准。

这意味着什么:

#include <cxxabi.h> // the libstdc++ used by g++ does contain this header

template <typename type>
void print(const type *addr) // you wanted a pointer
{
  char * name = abi::__cxa_demangle(typeid(*addr).name(), 0, 0, NULL);
  printf("type is: %s\n", name);
  free(name);
}
     
print(new unsigned long);    // prints "type is: unsigned long"
print(new std::vector<int>); // prints "type is: std::vector<int, std::allocator<int> >"

编辑:纠正了内存泄漏。谢谢杰西。


8
投票

有Boost.TypeIndex库。

有关详细信息,请参阅 boost::typeindex::type_id 。

它非常易于使用,跨平台,是真正的编译型解决方案。即使没有可用的 RTTI,它也能正常工作。大多数编译器也都得到了支持。


1
投票

您可以使用 C++20 的

concept
来定义任何类型都无法满足的约束。在模板参数上使用此约束并实例化模板将导致编译器失败并打印出推导的类型名。

void print_typename (auto) requires false {}
// Equivalent to
//   template<typename T>
//   concept unsatisfiable_c = false;
//   
//   template<unsatisfiable_c T>
//   void print_typename (T) {}

int main (int, char**) {
    int foo = 73;
    print_typename(foo);
    // error: no matching function for call to 'print_typename'
    // note: candidate template ignored: constraints not satisfied [with auto:1 = int]
    //                                                                            ^^^

    return 0;
}

如果您的

T
更复杂,例如
T=std::vector<std::pair<key_t, value_t>>
T=void (*)(U, V const&)
,并且您想具体找出
key_t
V
,您可以按如下方式操作:

template<typename T>
concept unsatisfiable_c = false;

template<unsatisfiable_c key_t, typename value_t>
void print_key_t (std::vector<std::pair<key_t, value_t>>) {}

template<typename U, unsatisfiable_c V>
void print_V (void (*)(U, V const&)) {}

void foo (double, long double const&) {}

int main (int, char**) {
    std::vector<std::pair<long, char>> bar;

    print_key_t(bar);
    // error: no matching function for call to 'print_key_t'
    // note: candidate template ignored: constraints not satisfied [with key_t = long, value_t = char]

    print_V(&foo);
    // error: no matching function for call to 'print_V'
    // note: candidate template ignored: constraints not satisfied [with U = double, V = long double]

    return 0;
}

0
投票

如果你不关心该语句会破坏编译,最简单的方法是: static_assert( 1== T::not_existed_member_name, "编译时打印 T 类型" )

无法在不破坏编译的情况下打印类型。 (当然你可以在这里触发警告,而不是错误)


-2
投票

如果您有一组已知的类型用于实例化模板,我们可以使用此旧线程中描述的方法: stackoverflow.com/questions/1055452

© www.soinside.com 2019 - 2024. All rights reserved.