【问题标题】:How to get a fully-qualified function name in C++ (gcc), _excluding_ the return type?如何在 C++ (gcc) 中获得完全限定的函数名,_不包括_返回类型?
【发布时间】:2015-01-08 09:35:26
【问题描述】:

This question 描述了如何使用__PRETTY_FUNCTION__ 获取函数的全名,包括它的返回类型、参数类型、命名空间和模板参数。

考虑以下漂亮的功能:

namespace foo {
namespace {

template<int i>
int (*bar(int (*arg)(int *)))(int *) {
    printf("%s\n", __PRETTY_FUNCTION__);

    return arg;
}

} // anonymous namespace
} // namespace foo

如果您不明白,该函数将获取并返回一个指向 int * -> int 函数的指针。

它漂亮的名字是,当使用g++ (4.9) 编译时,

int (* foo::{anonymous}::bar(int (*)(int*)))(int*) [with int i = 1337]

以及,clang++ (3.5),

int (*foo::(anonymous namespace)::bar(int (*)(int *)) [i = 1337])(int *)

这些字符串非常不适合测试函数是否是某个命名空间的一部分。有没有其他方法,或者说,编译器提供的库来解析这些字符串?

为了澄清,我宁愿有类似的东西

foo::{anonymous}::bar <I don't care about anything beyond this point>

更理想的是,我想要一种编译时方式,例如 constexpr 函数 split(__PRETTY_FUNCTION__) 产生某种列表

  • 完全限定的函数名
  • 返回类型
  • arg0 的类型
  • arg1 的类型

等等,但我很乐意只使用完全限定的函数名称。

【问题讨论】:

  • 听起来你只想要__FUNCTION__ 宏?
  • __FUNCTION__ 只会给我bar,这还不够。 __func__ 也是。
  • 所以你想要命名空间和名称,而不是返回类型和参数?为什么?
  • 这用于项目的日志子系统。我希望能够设置日志记录规则,例如“不记录来自命名空间 foo 或 C 类的 DBG 消息”。实际完全限定的函数名称前面的不确定性混乱使得这不可能。
  • 所以你想做printf(%s\n", modify(__PRETTY_FUNCTION__));之类的事情,而modify()只是一些字符串处理函数?

标签: c++ gcc


【解决方案1】:

经过更仔细的观察,我写了这段代码:

template <typename InputIterator, typename T>
InputIterator findClosing( InputIterator first, InputIterator last, T close )
{
    if (first == last)
        return last;

    auto open = *first;
    unsigned counter = 1;
    while (++first != last)
    {
        if (*first == close && --counter == 0)
            return first;
        if (*first == open)
            ++counter;
    }

    return last;
}

template <std::size_t N,
          std::size_t N2>
std::string f(char const(&str)[N], char const(&name)[N2])
{
    using namespace std;

    // Argument to isalnum must be unsigned:
    auto cond = [] (unsigned char c) {return !isalnum(c) && c != '_';};

    auto iter = str;
    for (;;++iter)
    {
        iter = search( iter, end(str),
                       begin(name), end(name)-1 );

        if (iter == end(str))
            throw invalid_argument("");

        if ((iter == begin(str)      || cond(iter[-1]))
         && (iter ==   end(str) - N2 || (cond(iter[N2-1]) && iter[N2-1] != ':')))
            break;
    }

    auto origin_iter = iter;
    while(iter != begin(str))
    {
        --iter;
        for (auto p : {"()", "{}"})
        if (*iter == p[1])
            iter = findClosing(reverse_iterator<char const*>(iter+1),
                               reverse_iterator<char const*>(begin(str)),
                               p[0]).base()-2;

        if (cond(*iter) && *iter != ':')
            return string(iter+1, origin_iter+N2-1);
    }

    return string(iter, origin_iter+N2-1);
}

它应该适用于任何函数,假设__PRETTY_FUNCTION__ 中不存在不必要的空格并且__func__ 仅包含不合格的函数名称。

Demo.

【讨论】:

    【解决方案2】:

    这并不能完全满足您的要求,因为它不会返回单个值,即 constexpr 字符串,但它会接近。然而,它完全是constexpr,它返回一个指向命名空间开头的指针,一个指向它结尾的指针(函数名的开头),以及可选的该字符串的长度。

    constexpr bool isNotIdentifierChar(const char *pf)
    {
        return !isalnum(*pf) && *pf!='_';
    }
    
    constexpr const char* getNamespaceEnd(const char *pf, const char *func)
    {
        return (isNotIdentifierChar(pf) && 0==strncmp(&pf[1], func, strlen(func))
        && isNotIdentifierChar(pf+strlen(func)+1) && ':'!=pf[strlen(func)+1])  
            ? &pf[1] 
            : getNamespaceEnd(++pf, func);
    }
    
    constexpr const char* getNamespaceStartIter(const char *pf, const char *end)
    {
        return (*pf==' ' && strchr(&pf[1], ' ') > end) 
            ? &pf[1] 
            : getNamespaceStartIter(++pf, end);
    }
    
    constexpr const char* getNamespaceStart(const char *pf, const char *func)
    {
        return getNamespaceStartIter(pf, getNamespaceEnd(pf, func));
    }
    
    constexpr size_t getNamespaceSize(const char *pf, const char *func)
    {
        return getNamespaceEnd(pf, func) - getNamespaceStart(pf, func);
    }
    

    不能让constexpr 返回std::string,因为std::string(或任何等效构造)有一个非平凡的析构函数,但我们可以像这样返回命名空间的开始和结束:

    printf("%s\n", std::string(
            getNamespaceStart(__PRETTY_FUNCTION__, __func__),
            getNamespaceEnd(__PRETTY_FUNCTION__, __func__)+strlen(__func__)
        ).data());
    

    此版本包括函数名称(在本例中为“bar”),但也可以通过简单地省略 +strlen(__func__) 来省略它。

    我们还可以通过使用一个简单的类使其更简洁:

    class NamespaceString {
    public:
        NamespaceString(const char *pf, const char *func) 
            : start(getNamespaceStart(pf, func)),
              end(getNamespaceEnd(pf, func)) {}
        std::string getString() const {
            return std::string(start, end);
        }
    private:
        const char *start;
        const char *end;
    };
    

    那么函数内部的使用就干净了一点:

    static const NamespaceString ns(__PRETTY_FUNCTION__, __func__);
    printf("%s\n", ns.getString().data());
    

    更新的演示包括@Columbo 的答案和这个:live code

    【讨论】:

    • strlenstr(n)cmpconstexpr?有趣...我昨天将它们重新实现为constexpr...
    • 您和 Columbo 的方法似乎都是在 __PRETTY_FUNCTION__ 中搜索 __func__ 子字符串,然后向后搜索直到遇到第一个空格...构造一个示例不是比较简单吗这在哪里失败了,可能很可怕?例如float mynamespace::f()?
    • @mic_e 是的,我的第一种方法是非常错误的。但是,新代码应该可以处理这些功能。
    • @mic_e:是的,我的函数很容易被愚弄而导致可怕的失败——一方面,它不适用于 clang。两个答案都失败,嵌套命名空间与函数名称相同。
    • 我已经修改了我的答案以正确处理与函数名称相同的嵌套命名空间。
    猜你喜欢
    • 2014-10-25
    • 2013-11-27
    • 1970-01-01
    • 1970-01-01
    • 2010-11-11
    • 2015-05-17
    • 2014-01-13
    • 2021-06-16
    • 1970-01-01
    相关资源
    最近更新 更多