誰かがこれらのコンパイル時定数を達成するためのよりエレガントな方法を推奨できますか?
template <int> struct Map;
template <> struct Map<0> {static const int value = 4;};
template <> struct Map<1> {static const int value = 8;};
template <> struct Map<2> {static const int value = 15;};
template <int> struct MapInverse;
template <> struct MapInverse<4> {static const int value = 0;};
template <> struct MapInverse<8> {static const int value = 1;};
template <> struct MapInverse<15> {static const int value = 2;};
私のプログラムでは値をconstexprにする必要がありますが、逆マップされた値は更新するのが面倒になります(そして、間違いを犯したり、忘れたりしやすくなります)。
C++ 11を使用した線形検索の別のTMPアプローチ:
#include <type_traits>
// === Types:
// Usage:
// Function<Map<x1,y1>,Map<x2,y2>,...>
template<int D, int R> struct Map { enum { domain=D, range=R }; };
template<typename ...A> struct Function {};
// === Metafunctions:
// Usage:
// ApplyFunction<x,F>::value
template<int I, typename M> struct ApplyFunction;
// Usage:
// ApplyFunctionInverse<x,F>::value
template<int I, typename M> struct ApplyFunctionInverse;
// ==== Example:
// Define function M to the mapping in your original post.
typedef Function<Map<0,4>,Map<1,8>,Map<2,15>> M;
// ==== Implementation details
template<typename T> struct Identity { typedef T type; };
template<int I, typename A, typename ...B> struct ApplyFunction<I, Function<A,B...> > {
typedef typename
std::conditional <I==A::domain
, Identity<A>
, ApplyFunction<I,Function<B...>> >::type meta;
typedef typename meta::type type;
enum { value = type::range };
};
template<int I, typename A> struct ApplyFunction<I, Function<A>> {
typedef typename
std::conditional <I==A::domain
, Identity<A>
, void>::type meta;
typedef typename meta::type type;
enum { value = type::range };
};
// Linear search by range
template<int I, typename A> struct ApplyFunctionInverse<I, Function<A>> {
typedef typename
std::conditional <I==A::range
, Identity<A>
, void>::type meta;
typedef typename meta::type type;
enum { value = type::domain };
};
template<int I, typename A, typename ...B> struct ApplyFunctionInverse<I, Function<A,B...> > {
typedef typename
std::conditional <I==A::range
, Identity<A>
, ApplyFunctionInverse<I,Function<B...>> >::type meta;
typedef typename meta::type type;
enum { value = type::domain };
};
// ==============================
// Demonstration
#include <iostream>
int main()
{
// Applying function M
std::cout << ApplyFunction<0,M>::value << std::endl;
std::cout << ApplyFunction<1,M>::value << std::endl;
std::cout << ApplyFunction<2,M>::value << std::endl;
// Applying function inverse M
std::cout << ApplyFunctionInverse<4,M>::value << std::endl;
std::cout << ApplyFunctionInverse<8,M>::value << std::endl;
std::cout << ApplyFunctionInverse<15,M>::value << std::endl;
}
私はこのアプリケーションにzchのC++ 11ソリューションを好みますが、おそらく誰かがこのアプローチに価値を見いだすでしょう。
このC++ 11ソリューションでは、すべてのマップアイテムがconstexpr
配列に保持され、キーまたは値のいずれかで検索するconstexpr
再帰関数があります。
#include <utility>
using Item = std::pair<int, int>;
constexpr Item map_items[] = {
{ 6, 7 },
{ 10, 12 },
{ 300, 5000 },
};
constexpr auto map_size = sizeof map_items/sizeof map_items[0];
static constexpr int findValue(int key, int range = map_size) {
return
(range == 0) ? throw "Key not present":
(map_items[range - 1].first == key) ? map_items[range - 1].second:
findValue(key, range - 1);
};
static constexpr int findKey(int value, int range = map_size) {
return
(range == 0) ? throw "Value not present":
(map_items[range - 1].second == value) ? map_items[range - 1].first:
findKey(value, range - 1);
};
static_assert(findKey(findValue(10)) == 10, "should be inverse");
これにはマクロを使用します:
template <int> struct Map;
template <int> struct MapInverse;
#define MAP_ENTRY(i, j) \
template <> struct Map<i> {static const int value = j;}; \
template <> struct MapInverse<j> {static const int value = i;};
MAP_ENTRY (0, 4)
MAP_ENTRY (1, 8)
MAP_ENTRY (2, 15)
これにより、両方のマップの同期が維持されます。
マクロを使用しないソリューションですが、キーが間隔[0, MAP_SIZE)
からのものであるという仮定を使用します。
再帰テンプレートFindInverse
はMap
を最後から最初までスキャンして、指定された値を検索します。
template <int> struct Map;
template <> struct Map<0> {static const int value = 4;};
template <> struct Map<1> {static const int value = 8;};
template <> struct Map<2> {static const int value = 15;};
const int MAP_SIZE = 3;
template <int x, int range> struct FindInverse {
static const int value = (Map<range - 1>::value == x)?
(range - 1):
(FindInverse<x, range - 1>::value);
};
template <int x> struct FindInverse<x, 0> {
static const int value = -1;
};
template <int x> struct MapInverse: FindInverse<x, MAP_SIZE> {
static_assert(MapInverse::value != -1, "x should be a value in Map");
};
static_assert(MapInverse<Map<1>::value>::value == 1, "should be inverse");
これは、バイナリ検索を利用するテンプレートメタプログラミング手法です。線形検索アプローチよりも効率が悪いと思いますが、他の人にとっては興味深いかもしれないと思いました。この解決策は改善できると確信しています。
#include <iostream>
template <int> struct Map { static const int value = INT_MIN; };
template <> struct Map<0> { static const int value = 4; };
template <> struct Map<1> { static const int value = 8; };
template <> struct Map<2> { static const int value = 15; };
// This searches the Map at POS 0 +/- a DELTA of 0x100
template
<
int x,
int POS = 0,
int DELTA = 0x100
>
struct MapInverse
{
typedef MapInverse<x, POS - (DELTA >> 1), (DELTA >> 1)> LEFT;
typedef MapInverse<x, POS + (DELTA >> 1), (DELTA >> 1)> RIGHT;
static const int MATCH_POS =
(Map<POS>::value == x)? POS:
(DELTA == 0)? INT_MIN:
(LEFT::MATCH_POS != INT_MIN)? LEFT::MATCH_POS:
RIGHT::MATCH_POS;
};
int main(int argc, const char * argv[])
{
// insert code here...
std::cout
<< MapInverse<0>::MATCH_POS << std::endl
<< MapInverse<1>::MATCH_POS << std::endl
<< MapInverse<2>::MATCH_POS << std::endl
<< MapInverse<3>::MATCH_POS << std::endl
<< MapInverse<4>::MATCH_POS << std::endl
<< MapInverse<5>::MATCH_POS << std::endl
<< MapInverse<6>::MATCH_POS << std::endl
<< MapInverse<7>::MATCH_POS << std::endl
<< MapInverse<8>::MATCH_POS << std::endl
<< MapInverse<9>::MATCH_POS << std::endl
<< MapInverse<10>::MATCH_POS << std::endl
<< MapInverse<11>::MATCH_POS << std::endl
<< MapInverse<12>::MATCH_POS << std::endl
<< MapInverse<13>::MATCH_POS << std::endl
<< MapInverse<14>::MATCH_POS << std::endl
<< MapInverse<15>::MATCH_POS << std::endl
<< MapInverse<16>::MATCH_POS << std::endl
<< MapInverse<17>::MATCH_POS << std::endl;
return 0;
}
これが一般的なconstexprマップのC++ 17実装です
#include <type_traits>
#include <Tuple>
//tag for typenames
template <class T>
struct tag_type
{
using type = T;
};
//tag for autos
template <auto val>
struct tag_auto
{
constexpr static decltype(val) value = val;
};
//generic pair
template <typename key_tag, typename val_tag>
struct cexpr_pair
{
using key = key_tag;
using value = val_tag;
};
template <class ... cexpr_pairs>
class cexpr_generic_map
{
template <typename cexpr_tag_key, size_t iter = 0>
constexpr static auto Find()
{
//failed to find by key
if constexpr (iter == sizeof...(cexpr_pairs))
return cexpr_pair<cexpr_tag_key, void>();
else
{
typedef std::Tuple_element_t<iter, std::Tuple<cexpr_pairs...>> cur_pair;
if constexpr (std::is_same_v<cexpr_tag_key, cur_pair::key>)
return cur_pair();
else
return Find<cexpr_tag_key, iter + 1>();
}
}
public:
template <typename tag_key>
using found_pair = decltype(Find<tag_key>());
};
(私が思うに)1つの値は1つの組み合わせでのみ使用できるので(あなたの場合は4-15、15-88にすることはできません)、実際には「2つの異なるマップ」を使用する必要はありません。初期値と初期値の両方を追加するだけです。そこに逆の値。
使用例:
typedef cexpr_generic_map<
//initial values
cexpr_pair<tag_auto<0>, tag_auto<4>>,
cexpr_pair<tag_auto<1>, tag_auto<8>>,
cexpr_pair<tag_auto<2>, tag_auto<15>>,
//inversed values
cexpr_pair<tag_auto<4>, tag_auto<0>>,
cexpr_pair<tag_auto<8>, tag_auto<1>>,
cexpr_pair<tag_auto<15>, tag_auto<2>>
> map_inverse;
//find initial
static_assert(map_inverse::found_pair<tag_auto<0>>::value::value == 4);
static_assert(map_inverse::found_pair<tag_auto<1>>::value::value == 8);
static_assert(map_inverse::found_pair<tag_auto<2>>::value::value == 15);
//find inversed
static_assert(map_inverse::found_pair<tag_auto<4>>::value::value == 0);
static_assert(map_inverse::found_pair<tag_auto<8>>::value::value == 1);
static_assert(map_inverse::found_pair<tag_auto<15>>::value::value == 2);
値によるルックアップを追加する(最初に一致するペアを見つける)こともできます。または、「それほど一般的ではない」ように変更して、cexpr_pairのテンプレート宣言でタグをautosだけに置き換えることもできます(同様に、キーと値の定義を変更します)。