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_t
、long 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_t
はlong 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
は一方向にしかならないからです。
私はここで答えを得ることにもあまり関心がありません。これは、例が恐ろしく不自然ではないときに誰も気にしないだろうと疑う超過激なEdgeのケースだからです(つまり、これはコミュニティ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コンセプトで使用するのが非常に一般的です)のような修飾子を使用しています。
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&)
がありません。
私は理解していることを強調したいと思いますなぜこれが起こっていますが、私はどこでもコードを繰り返すことを強制しない回避策を探しています。
このようなものを見るために64ビットに行く必要はありません。一般的な32ビットプラットフォームではint32_t
を検討してください。 typedef
またはint
としてlong
'edになりますが、明らかに2つのうちの1つだけです。 int
とlong
は、もちろん異なるタイプです。
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)
は、exactlyがfoo(long)
と同じ場合には定義されません。
[編集] C++ 11では、これを記述する標準的な方法が用意されました。
long foo(long x);
std::enable_if<!std::is_same<int64_t, long>::value, int64_t>::type foo(int64_t);
型がint64_tと同じ型であるかどうか、または何かが64ビットかどうかを知りたいですか?提案されたソリューションに基づいて、後者について質問していると思います。その場合、私は次のようなことをします
template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
だから私の質問は次のとおりです。longlong intと同様に、long long intもint64_tであることをコンパイラに伝える方法はありますか
これは良い質問か問題ですが、答えはノーだと思います。
また、long int
はlong 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*'