有没有办法在不同版本的g++编译器中使用typeid和typeinfo的解决方法?

huangapple go评论73阅读模式
英文:

Is there a workaround for using typeid and typeinfo in different version of g++ compiler?

问题

My program uses typeid() and typeinfo to check if the two types are identical at runtime.

The following is a minimal example.

lib.h

#ifndef _ACANE_LIB_H_
#define _ACANE_LIB_H_

#include <typeinfo>
#include <typeindex>
#include <tuple>

std::type_index* GetTypeIndex();
const char* GetTypeIndexName();

#endif

lib.cpp

#include "lib.h"

#include <tuple>

std::type_index* GetTypeIndex() {
    return new std::type_index(typeid(std::tuple<int, int>));
}

const char* GetTypeIndexName() {
    return typeid(std::tuple<int, int>).name();
}

main.cpp

#include <cstdio>
#include <cstring>
#include <cxxabi.h>
#include <string>
#include <memory>
#include "lib.h"

static std::string Demangle(const char* name) {
  int status = -4;
  std::unique_ptr<char, void(*)(void*)> res {
      abi::__cxa_demangle(name, NULL, NULL, &status),
      std::free
  };
  return (status==0) ? res.get() : name ;
}

int main() {
    std::type_index target = std::type_index(typeid(std::tuple<int, int>));
    std::type_index* expected = GetTypeIndex();
    if (target == *expected) {
        printf("yes\n");
    }
    else {
        printf("no\n");
    }

    const char* target_name = typeid(std::tuple<int, int>).name();
    const char* expected_name = GetTypeIndexName();
    if (!strcmp(target_name, expected_name)) {
        printf("yes\n");
    }
    else {
        printf("no\n");
        printf("target: %s\nexpected: %s\n", target_name, expected_name);
        std::string s1 = Demangle(target_name), s2 = Demangle(expected_name);
        printf("target: %s\nexpected: %s\n", target_name, expected_name);
        printf("target: %s\nexpected: %s\n", s1.c_str(), s2.c_str());
        printf("target: %p\nexpected: %p\n", target_name, expected_name);
    }
}

I compile the lib.cpp into a static library, and link the main.cpp with it, with the following commands

build_with_same_compiler.sh

#!/bin/bash

LIB_CC=/usr/gcc-4.9.4/bin/g++
LIB_AR=/usr/gcc-4.9.4/bin/gcc-ar
EXEC_CC=/usr/gcc-4.9.4/bin/g++
CXX_FLAGS="-std=c++11"

set -x

$LIB_CC -fPIC -c lib.cpp -o lib.o -std=c++11 $CXX_FLAGS
$LIB_AR cr libtesttypeinfo.a lib.o

$EXEC_CC -c main.cpp -o main.o $CXX_FLAGS
$EXEC_CC -o main_same main.o -L. -ltesttypeinfo

It works as I expected (obviously).

But when I compile the static lib and main.cpp with different compilers, it doesn't work. The typeid returns different type_info.

build_with_different_compiler.sh

#!/bin/bash

LIB_CC=/usr/gcc-4.9.4/bin/g++
LIB_AR=/usr/gcc-4.9.4/bin/gcc-ar
EXEC_CC=/usr/bin/g++-7
CXX_FLAGS="-std=c++11"

set -x

$LIB_CC -fPIC -c lib.cpp -o lib.o -std=c++11 $CXX_FLAGS
$LIB_AR cr libtesttypeinfo.a lib.o

$EXEC_CC -c main.cpp -o main.o $CXX_FLAGS
$EXEC_CC -o main_diff main.o -L. -ltesttypeinfo

The output is

no
no
target: St5tupleIJiiEE
expected: St5tupleIIiiEE
target: St5tupleIJiiEE
expected: St5tupleIIiiEE
target: std::tuple<int, int>
expected: std::tuple<int, int>
target: 0x5580ab7a36b8
expected: 0x5580ab7a374d

In g++ 4.9.4, the name of std::tuple<int, int> is St5tupleIJiiEE, while in g++ 7.5, it's St5tupleIIiiEE.

Since the type_info is implementation-defined, is there any workaround and stable method to check the type at runtime?

Another question is, std::any in C++17 also uses type_info, and how does std::any ensure the type_info is always the same between different versions of compilers?

英文:

My program uses typeid() and typeinfo to check if the two types is identical at runtime.

The following is a minimal example.


lib.h

#ifndef _ACANE_LIB_H_
#define _ACANE_LIB_H_

#include &lt;typeinfo&gt;
#include &lt;typeindex&gt;
#include &lt;tuple&gt;

std::type_index* GetTypeIndex();
const char* GetTypeIndexName();

#endif

lib.cpp

#include &quot;lib.h&quot;

#include &lt;tuple&gt;

std::type_index* GetTypeIndex() {
    return new std::type_index(typeid(std::tuple&lt;int, int&gt;));
}


const char* GetTypeIndexName() {
    return typeid(std::tuple&lt;int, int&gt;).name();
}

main.cpp

#include &lt;cstdio&gt;
#include &lt;cstring&gt;
#include &lt;cxxabi.h&gt;
#include &lt;string&gt;
#include &lt;memory&gt;
#include &quot;lib.h&quot;

static std::string Demangle(const char* name) {
  int status = -4;
  std::unique_ptr&lt;char, void(*)(void*)&gt; res {
      abi::__cxa_demangle(name, NULL, NULL, &amp;status),
      std::free
  };
  return (status==0) ? res.get() : name ;
}

int main() {
    std::type_index target = std::type_index(typeid(std::tuple&lt;int, int&gt;));
    std::type_index* expected = GetTypeIndex();;
    if (target == *expected) {
        printf(&quot;yes\n&quot;);
    }
    else {
        printf(&quot;no\n&quot;);
    }

    const char* target_name = typeid(std::tuple&lt;int, int&gt;).name();
    const char* expected_name = GetTypeIndexName();
    if (!strcmp(target_name, expected_name)) {
        printf(&quot;yes\n&quot;);
    }
    else {
        printf(&quot;no\n&quot;);
        printf(&quot;target: %s\nexpected: %s\n&quot;, target_name, expected_name);
        std::string s1 = Demangle(target_name), s2 = Demangle(expected_name);
        printf(&quot;target: %s\nexpected: %s\n&quot;, target_name, expected_name);
        printf(&quot;target: %s\nexpected: %s\n&quot;, s1.c_str(), s2.c_str());
        printf(&quot;target: %p\nexpected: %p\n&quot;, target_name, expected_name);
    }
}

I compile the lib.cpp into a static library, and link the main.cpp with it, with the following commands

build_with_same_compiler.sh

#!/bin/bash

LIB_CC=/usr/gcc-4.9.4/bin/g++
LIB_AR=/usr/gcc-4.9.4/bin/gcc-ar
EXEC_CC=/usr/gcc-4.9.4/bin/g++
CXX_FLAGS=&quot;-std=c++11&quot;

set -x

$LIB_CC -fPIC -c lib.cpp -o lib.o -std=c++11 $CXX_FLAGS
$LIB_AR cr libtesttypeinfo.a lib.o

$EXEC_CC -c main.cpp -o main.o $CXX_FLAGS
$EXEC_CC -o main_same main.o -L. -ltesttypeinfo

It works as I expected (obviously).


But when I compile the static lib and main.cpp with different compiler, it doesn't work. The typeid returns different type_info.

build_with_different_compiler.sh

#!/bin/bash

LIB_CC=/usr/gcc-4.9.4/bin/g++
LIB_AR=/usr/gcc-4.9.4/bin/gcc-ar
# EXEC_CC=/usr/gcc-12.1/bin/g++
EXEC_CC=/usr/bin/g++-7
CXX_FLAGS=&quot;-std=c++11&quot;

set -x

$LIB_CC -fPIC -c lib.cpp -o lib.o -std=c++11 $CXX_FLAGS
$LIB_AR cr libtesttypeinfo.a lib.o

$EXEC_CC -c main.cpp -o main.o $CXX_FLAGS
$EXEC_CC -o main_diff main.o -L. -ltesttypeinfo

The output is

no
no
target: St5tupleIJiiEE
expected: St5tupleIIiiEE
target: St5tupleIJiiEE
expected: St5tupleIIiiEE
target: std::tuple&lt;int, int&gt;
expected: std::tuple&lt;int, int&gt;
target: 0x5580ab7a36b8
expected: 0x5580ab7a374d

In g++4.9.4, the name of std::tuple&lt;int, int&gt; is St5tupleIJiiEE, while in g++7.5, it's St5tupleIIiiEE.

Since the type_info is implementation-defined, is there any workaround and stable method to check type at runtime?

Another question is, std::any in C++17 also uses type_info, and how does std::any ensure the type_info s always the same between different version of compilers?

答案1

得分: 3

这是模板参数包名称混淆的差异。GCC 4.9 默认使用I前缀(这是不正确的),而GCC 7.5 默认使用J前缀(这是正确的)。

在GCC中,可以通过-fabi-version来控制这种名称混淆。您可以在两个编译器上将此选项设置为相同的值,以使它们使用兼容的名称混淆。有关更多信息,请参见 https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html#index-fabi-version

英文:

This is a difference in the mangling of template argument packs. GCC 4.9 defaults to use the I prefix (which is incorrect), while GCC 7.5 defaults to use the J prefix (which is correct).

In GCC, this mangling can be controlled by -fabi-version. You can set this option to the same value on both compilers, to make them use compatible mangling. See https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html#index-fabi-version for more information.

答案2

得分: 2

没有。实际上,typeid、typeinfo和特别是name()可以在同一程序运行的不同时间返回不同的值,即使是相同的类型。

对于给定的类型,在同一程序运行期间,您将获得一致的结果。

但是,下次程序运行时,情况将被重置,一切都不确定。

另一个问题是,C++17中的std::any也使用type_info,std::any如何确保在不同版本的编译器之间type_info始终相同?

它不会这样做。它不需要这样做。std::any只比较在同一程序运行期间创建的对象的类型。这是有保证的一致性。在std::any中,没有任何设施可以保存特定对象的类型,然后在随后的程序运行中取出它以进行比较。

英文:

> is there any workaround and stable method to check type at runtime?

No. In fact, typeid, typeinfo and specifically name() can return different values every time you run the same program, for the same types.

For a given type, you'll get consistent results during the same program run.

The slate is wiped clean the next time the program runs. All bets are off.

> Another question is, std::any in C++17 also uses type_info, and how does std::any ensure the type_info s always the same between different version of compilers?

It doesn't. It does not need to. All that std::any compares are types of objects that are created during the same program run. This is guaranteed to be consistent. There are no facilities of any kind, in std::any to save the type of a specific object somewhere, and then fetch it out during a subsequent program run for comparison purposes.

答案3

得分: 0

我找到了解决方法。

受到MediaPipe的TypeID的启发,我使用参数化模板函数的地址来确定类型。

以下是代码:

lib.h

#ifndef _ACANE_LIB_H_
#define _ACANE_LIB_H_

#include <typeinfo>

template <class T>
void TypeTag();

template <class T>
void TypeTag2() { printf("TypeTag2: GCC %d.%d\n", __GNUC__, __GNUC_MINOR__); }

class TypeID {
public:
    template <class T>
    static TypeID Of() {
        TypeID type_id;
        type_id.get_ = TypeTag<T>;
        type_id.get2_ = TypeTag2<T>;
        type_id.name_mangle_ = typeid(T).name();
        return type_id;
    }

    bool operator== (TypeID another) {
        return another.get_ == get_;
    }

    bool operator< (TypeID another) {
        return another.get_ < get_;
    }

    bool operator!= (TypeID another) {
        return another.get_ != get_;
    }

    const char* name() {
        return name_mangle_;
    }

public:
    decltype(TypeTag<void>)* get_;
    decltype(TypeTag2<void>)* get2_;
    const char* name_mangle_;
};

TypeID GetTypeID();

#endif

lib.cpp

#include "lib.h"

#include <tuple>

template <class T>
void TypeTag() { }

template void TypeTag<std::tuple<int, int>>();

TypeID GetTypeID() {
    return TypeID::Of<std::tuple<int, int>>();
}

main.cpp

#include <cstdio>
#include <cstring>
#include <cxxabi.h>
#include <string>
#include <memory>
#include "lib.h"

static std::string Demangle(const char* name) {
  int status = -4;
  std::unique_ptr<char, void(*)(void*)> res {
      abi::__cxa_demangle(name, NULL, NULL, &status),
      std::free
  };
  return (status==0) ? res.get() : name ;
}

int main() {
    TypeID target = TypeID::Of<std::tuple<int, int>>();
    TypeID expected = GetTypeID();

    TestABI(std::tuple<int, int>{1, 2});

    if (target == expected) {
        printf("yes\n");
    } else {
        printf("no\n");
        
    }
    printf("get  target: %p\nexpected: %p\n", target.get_, expected.get_);
    printf("get2  target: %p\nexpected: %p\n", target.get2_, expected.get2_);
    target.get2_();
    expected.get2_();
}

输出是:

yes
get  target: 0x40113d
expected: 0x40113d
get2  target: 0x400d8f
expected: 0x40118d
TypeTag2: GCC 12.1
TypeTag2: GCC 4.9

在上述代码中,gcc 4.9.4中TypeTag<std::tuple<int, int>>的名称是_Z7TypeTagISt5tupleIJiiEEEvv。在12.1中,它是_Z7TypeTagISt5tupleIIiiEEEvv

lib.h中,我没有提供TypeTag函数的实现,因为C++保证源代码级别的兼容性,链接器将_Z7TypeTagISt5tupleIIiiEEEvv链接到_Z7TypeTagISt5tupleIJiiEEEvv,它们共享相同的地址。然而,编译器将分别生成两个不同版本的TypeTag2,它们具有不同的地址。

我没有找到关于链接器执行此行为的任何规范文档,但通过运行nm观察到了这一点。

nm main_diff|grep TypeTag
000000000040113d W _Z7TypeTagISt5tupleIIiiEEEvv
000000000040113d W _Z7TypeTagISt5tupleIJiiEEEvv
000000000040118d W _Z8TypeTag2ISt5tupleIIiiEEEvv
0000000000400d8f W _Z8TypeTag2ISt5tupleIJiiEEEvv

然而,仍然存在一些限制。

  • 函数必须明确或隐含指定。
  • 由于在头文件中未提供TypeTag函数的定义,因此库代码中未指定类型的TypeTag将导致链接错误(对TypeTag<some_type>的未定义引用)。
英文:

I found a workaround for my case.

Inspired from MediaPipe's TypeID , I use address of parameterized template functions to determine the types.


Here is the code

lib.h

#ifndef _ACANE_LIB_H_
#define _ACANE_LIB_H_

#include &lt;typeinfo&gt;

template &lt;class T&gt;
void TypeTag();

template &lt;class T&gt;
void TypeTag2() { printf(&quot;TypeTag2: GCC %d.%d\n&quot;, __GNUC__, __GNUC_MINOR__); }

class TypeID {
public:
    template &lt;class T&gt;
    static TypeID Of() {
        TypeID type_id;
        type_id.get_ = TypeTag&lt;T&gt;;
        type_id.get2_ = TypeTag2&lt;T&gt;;
        type_id.name_mangle_ = typeid(T).name();
        return type_id;
    }

    bool operator== (TypeID another) {
        return another.get_ == get_;
    }

    bool operator&lt; (TypeID another) {
        return another.get_ &lt; get_;
    }

    bool operator!= (TypeID another) {
        return another.get_ != get_;
    }

    const char* name() {
        return name_mangle_;
    }

public:
    decltype(TypeTag&lt;void&gt;)* get_;
    decltype(TypeTag2&lt;void&gt;)* get2_;
    const char* name_mangle_;
};

TypeID GetTypeID();

#endif

lib.cpp

#include &quot;lib.h&quot;

#include &lt;tuple&gt;

template &lt;class T&gt;
void TypeTag() { }

template void TypeTag&lt;std::tuple&lt;int, int&gt;&gt;();

TypeID GetTypeID() {
    return TypeID::Of&lt;std::tuple&lt;int, int&gt;&gt;();
}

main.cpp

#include &lt;cstdio&gt;
#include &lt;cstring&gt;
#include &lt;cxxabi.h&gt;
#include &lt;string&gt;
#include &lt;memory&gt;
#include &quot;lib.h&quot;

static std::string Demangle(const char* name) {
  int status = -4;
  std::unique_ptr&lt;char, void(*)(void*)&gt; res {
      abi::__cxa_demangle(name, NULL, NULL, &amp;status),
      std::free
  };
  return (status==0) ? res.get() : name ;
}

int main() {
    TypeID target = TypeID::Of&lt;std::tuple&lt;int, int&gt;&gt;();
    TypeID expected = GetTypeID();

    TestABI(std::tuple&lt;int, int&gt;{1, 2});

    if (target == expected) {
        printf(&quot;yes\n&quot;);
    } else {
        printf(&quot;no\n&quot;);
        
    }
    printf(&quot;get  target: %p\nexpected: %p\n&quot;, target.get_, expected.get_);
    printf(&quot;get2  target: %p\nexpected: %p\n&quot;, target.get2_, expected.get2_);
    target.get2_();
    expected.get2_();
}

The output is

yes
get  target: 0x40113d
expected: 0x40113d
get2  target: 0x400d8f
expected: 0x40118d
TypeTag2: GCC 12.1
TypeTag2: GCC 4.9

In above code, the mangled name of TypeTag&lt;std::tuple&lt;int, int&gt;&gt; in gcc 4.9.4 is _Z7TypeTagISt5tupleIJiiEEEvv. In 12.1, it's _Z7TypeTagISt5tupleIIiiEEEvv.

In lib.h, I did not provide an implementation for TypeTag function, because C++ guarantees source level compatibility, the linker will link _Z7TypeTagISt5tupleIIiiEEEvv to _Z7TypeTagISt5tupleIJiiEEEvv, they shared the same address. While the compilers will generate two different versions for TypeTag2 respectively, which have different address.

I didn't find any specification document for linker on performing this behavior but I observed this by running nm.

nm main_diff|grep TypeTag
000000000040113d W _Z7TypeTagISt5tupleIIiiEEEvv
000000000040113d W _Z7TypeTagISt5tupleIJiiEEEvv
000000000040118d W _Z8TypeTag2ISt5tupleIIiiEEEvv
0000000000400d8f W _Z8TypeTag2ISt5tupleIJiiEEEvv

There are still some limitations.

  • The function must explicitly or implicitly specified
  • Because definition of TypeTag is not presented in header file, any type of unspecified type for TypeTag in library code will cause a linking error (undefined reference to TypeTag<some_type>).

huangapple
  • 本文由 发表于 2023年7月27日 20:00:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/76779525.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定