【问题标题】:Compile-time map and inverse map values编译时映射和逆映射值
【发布时间】:2014-11-22 10:24:10
【问题描述】:

有人可以推荐一种更优雅的方式来实现这些编译时常量吗?

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,但是逆映射值更新起来很繁琐(而且很容易出错甚至忘记做)。

【问题讨论】:

  • 我觉得Map 很优雅。问题似乎是InverseMap的可维护性。

标签: c++ templates map meta inverse


【解决方案1】:

在这个 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");

【讨论】:

  • 因此,拥有任何通用关键域的牺牲是对反向和非反向查找进行搜索?
  • @prestokeys,我什至不确定这是不是很大的牺牲。我希望编译器能够记忆 constexpr 调用并消除编译时间损失。此外,constexpr 函数的编译速度通常比大量模板实例化要快得多。我只保留我原来的解决方案,因为它适用于 C++03(不包括 static_assert)。
  • 如果没有找到key,是否可以产生编译错误,而不是在运行时抛出异常?
  • @user877329,使用std::integral_constant&lt;int, findKey(10)&gt;::value 应该可以工作。
【解决方案2】:

使用 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 解决方案,但也许有人会发现这种方法的价值。

【讨论】:

  • 感谢您的加入。这看起来是迄今为止最通用的 MTP 解决方案。我知道可变参数模板迟早会出现。
【解决方案3】:

我会为此使用宏:

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)

这使两个地图保持同步。

【讨论】:

  • 谢谢!只是好奇,有什么方法可以在不使用宏的情况下实现这一点?
  • @prestokeys:我不知道。可能,但这可能会更复杂。
【解决方案4】:

没有宏的解决方案,但假设键来自区间[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");

【讨论】:

  • 漂亮!不过,我看不出最后一个 static_assert 是如何变成假的。
  • @prestokeys,它不能,它是用来测试和演示的。
  • 对于大型地图,您可能会使用这种方法达到编译器模板扩展限制。我想知道搜索是否可以做一些更智能的事情,比如二分搜索。
  • @StilesCrisis。二分搜索中的顺序关系是什么?映射的值与键的顺序不同。
  • 但是,如果存在二进制搜索,则可能会删除键为 0,1,2,...MAP_SIZE-1 的(限制性)条件。我的直觉告诉我,zch 的解决方案还有改进的空间。
【解决方案5】:

这是我的通用 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>());
};

由于(我假设)一个值只能用于一种组合(在您的情况下,它是 4 - 15,不能是 15 - 88),因此实际上没有必要使用“两个不同的地图”,您可以简单地添加初始值和反转值都在那里。

使用示例:

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 替换标签(同样修改键和值定义) .

【讨论】:

    【解决方案6】:

    这是一种利用二进制搜索的模板元编程技术。我怀疑它比线性搜索方法效率低,但我认为它可能对其他人很有趣。我确信这个解决方案可以改进。

    #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;
    }
    

    【讨论】:

    • 不管它有什么表现,这绝对值得研究。谢谢你的关注。地图中只有 3 个元素,线性搜索可能看起来更快,但是当地图很大时,您的二分搜索可能会胜出?
    • 在当前状态下,它按树形顺序进行搜索,但没有任何好的方法可以提前终止搜索。无论如何,所有的“性能”结果只会影响编译速度;生成的代码应该可以随意使用准确的结果,而不会产生任何开销。不过,这对我来说是一个有趣的实验。
    • 稍微简化了代码,但复杂性没有真正的变化。
    猜你喜欢
    • 1970-01-01
    • 2019-07-14
    • 2012-07-11
    • 2020-07-31
    • 1970-01-01
    • 1970-01-01
    • 2018-08-25
    • 1970-01-01
    相关资源
    最近更新 更多