次の場合:
template<typename T>
class A
{
public:
static const unsigned int ID = ?;
};
IDですべてのTに対して一意のコンパイル時IDを生成したい。__COUNTER__
とブーストPPライブラリを検討しましたが、これまで成功していません。これを実現するにはどうすればよいですか?
編集:IDはswitchステートメントの場合と同じように使用できる必要があります
Edit2:静的メソッドまたはメンバーのアドレスに基づくすべての回答が正しくありません。それらは一意のIDを作成しますが、コンパイル時に解決されないため、switchステートメントの場合として使用することはできません。
これは、(1つの定義規則に関して)標準に準拠したコンパイラーを想定すると十分です。
template<typename T>
class A
{
public:
static char ID_storage;
static const void * const ID;
};
template<typename T> char A<T>::ID_storage;
template<typename T> const void * const A<T>::ID= &A<T>::ID_storage;
C++標準3.2.5から1つの定義ルール[basic.def.odr](太字の強調鉱山):
... Dがテンプレートであり、複数の翻訳単位で定義されている場合、上記のリストの最後の4つの要件は、テンプレート定義(14.6.3)で使用されるテンプレートの囲みスコープの名前に適用されます。インスタンス化の時点での依存名(14.6.2)。 Dの定義がこれらすべての要件を満たしている場合、プログラムはDの単一の定義があるかのように動作します。 Dの定義がこれらの要件を満たさない場合、動作は未定義です。
私が通常使用するのはこれです:
template<typename>
void type_id(){}
using type_id_t = void(*)();
関数のすべてのインスタンス化には独自のアドレスがあるため、そのアドレスを使用してタイプを識別できます。
// Work at compile time
constexpr type_id_t int_id = type_id<int>;
// Work at runtime too
std::map<type_id_t, std::any> types;
types[type_id<int>] = 4;
types[type_id<std::string>] = "values"s
// Find values
auto it = types.find(type_id<int>);
if (it != types.end()) {
// Found it!
}
この回答 のコードを使用して、文字列からコンパイル時のHASHを生成することができます。
テンプレートを変更して1つの余分な整数を含め、マクロを使用して変数を宣言できる場合:
template<typename T, int ID> struct A
{
static const int id = ID;
};
#define DECLARE_A(x) A<x, COMPILE_TIME_CRC32_STR(#x)>
型宣言にこのマクロを使用すると、idメンバーには型名のハッシュが含まれます。例えば:
int main()
{
DECLARE_A(int) a;
DECLARE_A(double) b;
DECLARE_A(float) c;
switch(a.id)
{
case DECLARE_A(int)::id:
cout << "int" << endl;
break;
case DECLARE_A(double)::id:
cout << "double" << endl;
break;
case DECLARE_A(float)::id:
cout << "float" << endl;
break;
};
return 0;
}
タイプ名が文字列に変換されると、タイプ名のテキストを変更すると、異なるIDになります。例えば:
static_assert(DECLARE_A(size_t)::id != DECLARE_A(std::size_t)::id, "");
もう1つの欠点は、ハッシュの衝突が発生する可能性があることです。
これは私にとってはうまくいくようです:
template<typename T>
class Counted
{
public:
static int id()
{
static int v;
return (int)&v;
}
};
#include <iostream>
int main()
{
std::cout<<"Counted<int>::id()="<<Counted<int>::id()<<std::endl;
std::cout<<"Counted<char>::id()="<<Counted<char>::id()<<std::endl;
}
私は最近、この正確な問題に遭遇しました。私の解決策:
counter.hpp
class counter
{
static int i;
static nexti()
{
return i++;
}
};
Counter.cpp:
int counter::i = 0;
templateclass.hpp
#include "counter.hpp"
template <class T>
tclass
{
static const int id;
};
template <class T>
int tclass<T>::id = counter::nexti();
これは、switchステートメントで使用できないという1つの例外を除いて、MSVCおよびGCCで正しく機能するように見えます。
さまざまな理由で、私は実際にさらに進んで、共通ベースから派生する静的ID(上記のように)を持つ指定された名前パラメーターから新しいクラスを作成するプリプロセッサーマクロを定義しました。
静的関数のメモリアドレスを使用します。
template<typename T>
class A {
public:
static void ID() {}
};
(&(A<int>::ID))
は(&(A<char>::ID))
などとは異なります。
this 定数式カウンターの使用:
template <class T>
class A
{
public:
static constexpr int ID() { return next(); }
};
class DUMMY { };
int main() {
std::cout << A<char>::ID() << std::endl;
std::cout << A<int>::ID() << std::endl;
std::cout << A<BETA>::ID() << std::endl;
std::cout << A<BETA>::ID() << std::endl;
return 0;
}
出力:(GCC、C++ 14)
1
2
3
3
欠点は、定数式カウンターが機能するために、派生クラスの数の上限を推測する必要があることです。
わかりました.....これは私が this ウェブサイトから見つけたハックです。動作するはずです。あなたがする必要がある唯一のことは、カウンター「メタオブジェクト」を取るあなたのstruct
に別のテンプレートパラメータを追加することです。 A
とint
、bool
、およびchar
はすべて一意のIDを持っていますが、int
が1
であるとは限りません。テンプレートが開始される順序は必ずしもわかっていないため、bool
は2
などになります。
別の注意:
これはMicrosoftVisual C++では機能しません
#include <iostream>
#include "meta_counter.hpp"
template<typename T, typename counter>
struct A
{
static const size_t ID = counter::next();
};
int main () {
typedef atch::meta_counter<void> counter;
typedef A<int,counter> AInt;
typedef A<char,counter> AChar;
typedef A<bool,counter> ABool;
switch (ABool::ID)
{
case AInt::ID:
std::cout << "Int\n";
break;
case ABool::ID:
std::cout << "Bool\n";
break;
case AChar::ID:
std::cout << "Char\n";
break;
}
std::cout << AInt::ID << std::endl;
std::cout << AChar::ID << std::endl;
std::cout << ABool::ID << std::endl;
std::cout << AInt::ID << std::endl;
while (1) {}
}
これがmeta_counter.hpp
です:
// author: Filip Roséen <[email protected]>
// source: http://b.atch.se/posts/constexpr-meta-container
#ifndef ATCH_META_COUNTER_HPP
#define ATCH_META_COUNTER_HPP
#include <cstddef>
namespace atch { namespace {
template<class Tag>
struct meta_counter {
using size_type = std::size_t;
template<size_type N>
struct ident {
friend constexpr size_type adl_lookup (ident<N>);
static constexpr size_type value = N;
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template<class Ident>
struct writer {
friend constexpr size_type adl_lookup (Ident) {
return Ident::value;
}
static constexpr size_type value = Ident::value;
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template<size_type N, int = adl_lookup (ident<N> {})>
static constexpr size_type value_reader (int, ident<N>) {
return N;
}
template<size_type N>
static constexpr size_type value_reader (float, ident<N>, size_type R = value_reader (0, ident<N-1> ())) {
return R;
}
static constexpr size_type value_reader (float, ident<0>) {
return 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template<size_type Max = 64>
static constexpr size_type value (size_type R = value_reader (0, ident<Max> {})) {
return R;
}
template<size_type N = 1, class H = meta_counter>
static constexpr size_type next (size_type R = writer<ident<N + H::value ()>>::value) {
return R;
}
};
}}
#endif /* include guard */
私は数ヶ月前に同様の問題を抱えていました。実行ごとに同じ識別子を定義する手法を探していました。
これが要件である場合、 ここ は、多かれ少なかれ同じ問題を調査する別の質問です(もちろん、それはその素晴らしい答えと一緒に来ます)。
とにかく私は提案された解決策を使用しませんでした。それは私がその時にしたことの説明に従います。
次のようにconstexpr
関数を定義できます。
static constexpr uint32_t offset = 2166136261u;
static constexpr uint32_t prime = 16777619u;
constexpr uint32_t fnv(uint32_t partial, const char *str) {
return str[0] == 0 ? partial : fnv((partial^str[0])*prime, str+1);
}
inline uint32_t fnv(const char *str) {
return fnv(offset, str);
}
次に、継承する次のようなクラス:
template<typename T>
struct B {
static const uint32_t id() {
static uint32_t val = fnv(T::identifier);
return val;
}
};
CRTPイディオムが残りを行います。
例として、次のように派生クラスを定義できます。
struct C: B<C> {
static const char * identifier;
};
const char * C::identifier = "ID(C)";
クラスごとに異なる識別子を指定する限り、タイプを区別するために使用できる一意の数値があります。
識別子は、派生クラスの一部である必要はありません。例として、トレイトを使用してそれらを提供できます。
template<typename> struct trait;
template<> struct trait { static const char * identifier; };
// so on with all the identifiers
template<typename T>
struct B {
static const uint32_t id() {
static uint32_t val = fnv(trait<T>::identifier);
return val;
}
};
利点:
短所:
これは、上記で説明した最小限の実用的な例に従います。ID
ステートメントでswitch
メンバーメソッドを使用できるようにコードを調整しました。
#include<type_traits>
#include<cstdint>
#include<cstddef>
static constexpr uint32_t offset = 2166136261u;
static constexpr uint32_t prime = 16777619u;
template<std::size_t I, std::size_t N>
constexpr
std::enable_if_t<(I == N), uint32_t>
fnv(uint32_t partial, const char (&)[N]) {
return partial;
}
template<std::size_t I, std::size_t N>
constexpr
std::enable_if_t<(I < N), uint32_t>
fnv(uint32_t partial, const char (&str)[N]) {
return fnv<I+1>((partial^str[I])*prime, str);
}
template<std::size_t N>
constexpr inline uint32_t fnv(const char (&str)[N]) {
return fnv<0>(offset, str);
}
template<typename T>
struct A {
static constexpr uint32_t ID() {
return fnv(T::identifier);
}
};
struct C: A<C> {
static constexpr char identifier[] = "foo";
};
struct D: A<D> {
static constexpr char identifier[] = "bar";
};
int main() {
constexpr auto val = C::ID();
switch(val) {
case C::ID():
break;
case D::ID():
break;
default:
break;
}
}
非定数式でID
を使用する場合は、次のようにidentifier
sをどこかに定義する必要があることに注意してください。
constexpr char C::identifier[];
constexpr char D::identifier[];
あなたがそれをしたら、あなたはこのようなことをすることができます:
int main() {
constexpr auto val = C::ID();
// Now, it is well-formed
auto ident = C::ID();
// ...
}
__DATE__
および__TIME__
マクロを使用してタイプ<T>
の一意の識別子を取得するC++コードを次に示します。
フォーマット:
// __DATE__ "??? ?? ????"
// __TIME__ "??:??:??"
これは質の悪いハッシュ関数です:
#define HASH_A 8416451
#define HASH_B 11368711
#define HASH_SEED 9796691 \
+ __DATE__[0x0] * 389 \
+ __DATE__[0x1] * 82421 \
+ __DATE__[0x2] * 1003141 \
+ __DATE__[0x4] * 1463339 \
+ __DATE__[0x5] * 2883371 \
+ __DATE__[0x7] * 4708387 \
+ __DATE__[0x8] * 4709213 \
+ __DATE__[0x9] * 6500209 \
+ __DATE__[0xA] * 6500231 \
+ __TIME__[0x0] * 7071997 \
+ __TIME__[0x1] * 10221293 \
+ __TIME__[0x3] * 10716197 \
+ __TIME__[0x4] * 10913537 \
+ __TIME__[0x6] * 14346811 \
+ __TIME__[0x7] * 15485863
unsigned HASH_STATE = HASH_SEED;
unsigned HASH() {
return HASH_STATE = HASH_STATE * HASH_A % HASH_B;
}
ハッシュ関数の使用:
template <typename T>
class A
{
public:
static const unsigned int ID;
};
template <>
const unsigned int A<float>::ID = HASH();
template <>
const unsigned int A<double>::ID = HASH();
template <>
const unsigned int A<int>::ID = HASH();
template <>
const unsigned int A<short>::ID = HASH();
#include <iostream>
int main() {
std::cout << A<float>::ID << std::endl;
std::cout << A<double>::ID << std::endl;
std::cout << A<int>::ID << std::endl;
std::cout << A<short>::ID << std::endl;
}
これはできません。静的オブジェクトへのアドレスは、一意のIDに最も近いアドレスですが、そのようなオブジェクトのアドレス(静的const積分であっても)を取得するには、どこかに定義する必要があります。 1つの定義規則に従って、これらはCPPファイル内で定義する必要がありますが、テンプレートであるため実行できません。ヘッダーファイル内で統計を定義すると、各コンパイルユニットは、もちろん異なるアドレスに実装された独自のバージョンを取得します。
主にテンプレートに基づいた可能な解決策は次のとおりです。
#include<cstddef>
#include<functional>
#include<iostream>
template<typename T>
struct wrapper {
using type = T;
constexpr wrapper(std::size_t N): N{N} {}
const std::size_t N;
};
template<typename... T>
struct identifier: wrapper<T>... {
template<std::size_t... I>
constexpr identifier(std::index_sequence<I...>): wrapper<T>{I}... {}
template<typename U>
constexpr std::size_t get() const { return wrapper<U>::N; }
};
template<typename... T>
constexpr identifier<T...> ID = identifier<T...>{std::make_index_sequence<sizeof...(T)>{}};
// ---
struct A {};
struct B {};
constexpr auto id = ID<A, B>;
int main() {
switch(id.get<B>()) {
case id.get<A>():
std::cout << "A" << std::endl;
break;
case id.get<B>():
std::cout << "B" << std::endl;
break;
}
}
これにはC++ 14が必要であることに注意してください。
シーケンシャルIDをタイプのリストに関連付けるために必要なことは、上記の例のように、そのリストをテンプレート変数に提供することだけです。
constexpr auto id = ID<A, B>;
その時点から、get
メソッドを使用して、指定されたタイプの指定されたIDを取得できます。
id.get<A>()
そしてそれがすべてです。要求に応じて、サンプルコードに示すように、switch
ステートメントで使用できます。
数値IDを関連付けるクラスのリストに型が追加されている限り、識別子は各コンパイル後および各実行中に同じであることに注意してください。
リストからタイプを削除する場合でも、例としてfakeタイプをプレースホルダーとして使用できます。
template<typename> struct noLonger { };
constexpr auto id = ID<noLonger<A>, B>;
これにより、A
に関連付けられたIDがなくなり、B
に指定されたIDが変更されなくなります。A
を完全に削除しない場合は、次のようなものを使用できます。
constexpr auto id = ID<noLonger<void>, B>;
または何でも。
もう1つの方法は、一意の静的メンバーフィールドData
を持つ次のクラスtype
を検討することです。
_template <class T>
class Data
{
public:
static const std::type_index type;
};
// do [static data member initialization](http://stackoverflow.com/q/11300652/3041008)
// by [generating unique type id](http://stackoverflow.com/q/26794944/3041008)
template <class T>
std::type_index const Data<T>::type = std::type_index(typeid(T));
_
出力を生成します(_MinGWx64-gcc4.8.4 -std=c++11 -O2
_)
_printf("%s %s\n", Data<int>::type.name(), Data<float>::type.name())
//prints "i f"
_
正確には整数IDやきれいに印刷可能な文字列でも、constexpr
でもありませんが、 (順序付けられていない)連想コンテナのインデックスとして使用されます 。
_Data.h
_ヘッダーが複数のファイルに含まれている場合にも機能するようです(同じhashCode()
値)。
使用するtype
ごとにDECLARE_ID(type)
を1行追加しても問題がない場合は、実用的な解決策を次に示します。
#include <iostream>
template<class> struct my_id_helper;
#define DECLARE_ID(C) template<> struct my_id_helper<C> { enum {value = __COUNTER__ }; }
// actually declare ids:
DECLARE_ID(int);
DECLARE_ID(double);
// this would result in a compile error: redefinition of struct my_id_helper<int>’
// DECLARE_ID(int);
template<class T>
class A
{
public:
static const unsigned int ID = my_id_helper<T>::value;
};
int main()
{
switch(A<int>::ID)
{
case A<int>::ID: std::cout << "it's an int!\n"; break;
case A<double>::ID: std::cout << "it's a double!\n"; break;
// case A<float>::ID: // error: incomplete type ‘my_id_helper<float>’
default: std::cout << "it's something else\n"; break;
}
}
単調でない値とintptr_t
許容可能:
template<typename T>
struct TypeID
{
private:
static char id_ref;
public:
static const intptr_t ID;
};
template<typename T>
char TypeID<T>::id_ref;
template<typename T>
const intptr_t TypeID<T>::ID = (intptr_t)&TypeID<T>::id_ref;
Intが必要な場合、または単調に増分する値が必要な場合は、静的コンストラクターを使用することが唯一の方法だと思います。
// put this in a namespace
extern int counter;
template<typename T>
class Counter {
private:
Counter() {
ID_val = counter++;
}
static Counter init;
static int ID_val;
public:
static const int &ID;
};
template<typename T>
Counter<T> Counter<T>::init;
template<typename T>
int Counter<T>::ID_val;
template<typename T>
const int &Counter<T>::ID = Counter<T>::ID_val;
// in a non-header file somewhere
int counter;
共有ライブラリとアプリケーション間で共有している場合、これらの手法はどちらも安全ではないことに注意してください。
template<typename T>
static void get_type_id() { void* x; new (x) T(); }
using type_id_t = void(*)();
最適化で正常に動作します