1. ホーム
  2. c++

[解決済み] C++におけるlong long intとlong intとint64_tの比較

2023-01-04 16:10:06

質問

私は 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ビットコンパイル(および32ビットと64ビットのMSVCによるコンパイル)のいずれにおいても、プログラムの出力は次のようになります。

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 intint64_t の型があるので、論理的には int64_t , long intlong 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 int はまた int64_t と同じように long int は?


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

また、私はここで答えを得ることにあまり関心がありません。これは超ド級のエッジケースであり、例が恐ろしく工夫されていないときには誰も気にしないと思われるからです(これはコミュニティWikiであるべきということでしょうか)。


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

void go(int64_t) { }

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

は、この例がまだコンパイル可能であることを示しています。 long long int は暗黙のうちに int64_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 Conceptsではかなり一般的に使用されています)。

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&) .

私が理解していることを強調したいのは なぜ

を理解していることを強調したいのですが、私はあちこちでコードを繰り返すことを強制されない回避策を探しています。

どのように解決するのですか?

64bitにしなくても、このようなことはあります。考察 int32_t を一般的な 32 ビットプラットフォームで考えてみましょう。それは typedef として編集されるかもしれません。 int として、または long としても使えますが、明らかに一度にどちらか一方しか使えません。 intlong はもちろん異なる型です。

とする回避策がないことは容易に理解できます。 int == int32_t == long を 32 ビット システム上で実行する回避策がないことは容易に理解できるでしょう。同じ理由で、32 ビットシステム上で long == int64_t == long long を 64 ビット システム上で作る方法はありません。

もしできたとしたら、その結果は foo(int) , foo(long)foo(long long) - というように、同じオーバーロードに対して2つの定義を持ってしまうのです。

正しい解決策は、テンプレートコードは通常、正確な型に依存するべきではなく、その型のプロパティに依存することです。全体の same_type のロジックは、特定のケースではまだOKかもしれません。

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

すなわち、オーバーロードの foo(int64_t) が定義されていない場合、それが まさに と同じ foo(long) .

[編集] C++11で、標準的な書き方ができるようになりました。

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

[編集] またはC++20

long foo(long x);
int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);