web-dev-qa-db-ja.com

C ++でのlong long int vs. long int vs. int64_t

C++型の特性を使用しているときに奇妙な振る舞いを経験し、誤解のために何も開いたままにしたくないため、問題をこの風変わりな小さな問題に絞り込みました。

次のようなプログラムがあるとします。

#include <iostream>
#include <cstdint>

template <typename T>
bool is_int64() { return false; }

template <>
bool is_int64<int64_t>() { return true; }

int main()
{
 std::cout << "int:\t" << is_int64<int>() << std::endl;
 std::cout << "int64_t:\t" << is_int64<int64_t>() << std::endl;
 std::cout << "long int:\t" << is_int64<long int>() << std::endl;
 std::cout << "long long int:\t" << is_int64<long long int>() << std::endl;

 return 0;
}

GCC(および32ビットと64ビットMSVC)を使用した32ビットコンパイルでは、プログラムの出力は次のようになります。

int:           0
int64_t:       1
long int:      0
long long int: 1

ただし、64ビットGCCコンパイルの結果のプログラムは次を出力します。

int:           0
int64_t:       1
long int:      1
long long int: 0

long long intは符号付き64ビット整数であり、すべての意図と目的において、long intおよびint64_tタイプと同一であるため、論理的にはint64_tlong intおよびlong long intは同等のタイプです。これらのタイプを使用するときに生成されるアセンブリは同一です。 stdint.hを見ると、その理由がわかります。

# if __WORDSIZE == 64
typedef long int  int64_t;
# else
__extension__
typedef long long int  int64_t;
# endif

64ビットコンパイルでは、int64_tlong intではなくlong long intです(明らかに)。

この状況の修正は非常に簡単です。

#if defined(__GNUC__) && (__WORDSIZE == 64)
template <>
bool is_int64<long long int>() { return true; }
#endif

しかし、これは恐ろしくハック的であり、うまくスケーリングしません(実質の実際の機能、uint64_tなど)。 だから私の質問は:long long intint64_tであるように、long intでもあることをコンパイラに伝える方法はありますか?


私の最初の考えは、C/C++型定義が機能するため、これは不可能だということです。基本データ型の型の等価性をコンパイラに指定する方法はありません。それはコンパイラの仕事であり(そして、それが多くのことを壊す可能性がある)、typedefは一方向にしかならないからです。

私はここで答えを得ることにもあまり関心がありません。これは、例が恐ろしく不自然ではないときに誰も気にしないだろうと疑う超過激なEdgeのケースだからです(つまり、これはコミュニティwikiであるべきでしょうか?) 。


追加:次のような簡単な例ではなく、テンプレートの部分的な特殊化を使用している理由:

void go(int64_t) { }

int main()
{
    long long int x = 2;
    go(x);
    return 0;
}

long long intint64_tに暗黙的に変換可能であるため、上記の例は引き続きコンパイルされます。


追加:これまでの唯一の答えは、型が64ビットかどうかを知りたいと仮定していることです。私はそれを気にしていると誤解させたくはありませんでした。おそらく、この問題がどこに現れるかについての例をもっと提供すべきだったでしょう。

template <typename T>
struct some_type_trait : boost::false_type { };

template <>
struct some_type_trait<int64_t> : boost::true_type { };

この例では、some_type_trait<long int>boost::true_typeになりますが、some_type_trait<long long int>はそうではありません。これはC++の型の考え方では理にかなっていますが、望ましくありません。

別の例は、same_type(C++ 0xコンセプトで使用するのが非常に一般的です)のような修飾子を使用しています。

template <typename T>
void same_type(T, T) { }

void foo()
{
    long int x;
    long long int y;
    same_type(x, y);
}

C++は(正しく)型が異なると認識するため、この例はコンパイルに失敗します。 g ++は、次のようなエラーでコンパイルに失敗します:一致する関数呼び出しsame_type(long int&, long long int&)がありません。

私は理解していることを強調したいと思いますなぜこれが起こっていますが、私はどこでもコードを繰り返すことを強制しない回避策を探しています。

78
Travis Gockel

このようなものを見るために64ビットに行く必要はありません。一般的な32ビットプラットフォームではint32_tを検討してください。 typedefまたはintとしてlong 'edになりますが、明らかに2つのうちの1つだけです。 intlongは、もちろん異なるタイプです。

32ビットシステムでint == int32_t == longを作成する回避策がないことを確認するのは難しくありません。同じ理由で、64ビットシステムでlong == int64_t == long longを作成する方法はありません。

可能であれば、foo(int)foo(long)、およびfoo(long long)をオーバーロードしたコードでは、考えられる結果はかなり苦痛になります。

正しい解決策は、通常、テンプレートコードは正確な型に依存するのではなく、その型のプロパティに依存することです。 same_typeロジック全体は、特定の場合でも問題ありません。

long foo(long x);
std::tr1::disable_if(same_type(int64_t, long), int64_t)::type foo(int64_t);

つまり、オーバーロードfoo(int64_t)は、exactlyfoo(long)と同じ場合には定義されません。

[編集] C++ 11では、これを記述する標準的な方法が用意されました。

long foo(long x);
std::enable_if<!std::is_same<int64_t, long>::value, int64_t>::type foo(int64_t);
47
MSalters

型がint64_tと同じ型であるかどうか、または何かが64ビットかどうかを知りたいですか?提案されたソリューションに基づいて、後者について質問していると思います。その場合、私は次のようなことをします

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
5
Logan Capaldo

だから私の質問は次のとおりです。longlong intと同様に、long long intもint64_tであることをコンパイラに伝える方法はありますか

これは良い質問か問題ですが、答えはノーだと思います。

また、long intlong long intではない場合があります。


# if __WORDSIZE == 64
typedef long int  int64_t;
# else
__extension__
typedef long long int  int64_t;
# endif

これはlibcだと思います。もっと深く行きたいと思う。

GCC(および32ビットと64ビットMSVC)を使用した32ビットコンパイルでは、プログラムの出力は次のようになります。

int:           0
int64_t:       1
long int:      0
long long int: 1

32ビットLinuxはILP32データモデルを使用します。整数、ロング、およびポインターは32ビットです。 64ビットタイプはlong longです。

Microsoftは、範囲を Data Type Ranges で文書化します。 long long__int64と同等です。

ただし、64ビットGCCコンパイルの結果のプログラムは次を出力します。

int:           0
int64_t:       1
long int:      1
long long int: 0

64ビットLinuxは、LP64データモデルを使用します。ロングは64ビットで、long longは64ビットです。 32ビットと同様に、Microsoftは Data Type Ranges で範囲を文書化し、long longは依然として__int64です。

すべてが64ビットであるILP64データモデルがあります。 Word32タイプの定義を取得するには、追加の作業が必要です。 64-Bit Programming Models:Why LP64? のような論文も参照してください


しかし、これは恐ろしくハック的であり、うまくスケールしません(物質の実際の機能、uint64_tなど)...

ええ、それはさらに良くなります。 GCCは、64ビット型をとるはずの宣言を組み合わせて一致させるため、特定のデータモデルを使用していても問題が発生しやすくなります。たとえば、次の場合はコンパイルエラーが発生し、-fpermissiveを使用するように指示されます。

#if __LP64__
typedef unsigned long Word64;
#else
typedef unsigned long long Word64;
#endif

// intel definition of rdrand64_step (http://software.intel.com/en-us/node/523864)
// extern int _rdrand64_step(unsigned __int64 *random_val);

// Try it:
Word64 val;
int res = rdrand64_step(&val);

結果:

error: invalid conversion from `Word64* {aka long unsigned int*}' to `long long unsigned int*'

したがって、LP64を無視して、次のように変更します。

typedef unsigned long long Word64;

次に、LP64を定義し、NEONを使用する64ビットARM IoTガジェットに移動します。

error: invalid conversion from `Word64* {aka long long unsigned int*}' to `uint64_t*'
1
jww