具有initializer_list的编译时查找表
Compile-Time Lookup-Table with initializer_list
假设您有一些哈希值,并希望在编译时将它们映射到各自的字符串。 理想情况下,我希望能够写一些类似的东西:
constexpr std::map<int, std::string> map = { {1, "1"}, {2 ,"2"} };
不幸的是,这在 C++17 和 C++2a 中都是不可能的。不过 我尝试用std::array
模拟它,但在编译时无法获取初始值设定项列表的大小,以便在不显式指定大小的情况下正确设置数组的类型。 这是我的模型:
template<typename T0, typename T1>
struct cxpair
{
using first_type = T0;
using second_type = T1;
// interestingly, we can't just = default for some reason...
constexpr cxpair()
: first(), second()
{ }
constexpr cxpair(first_type&& first, second_type&& second)
: first(first), second(second)
{ }
// std::pair doesn't have these as constexpr
constexpr cxpair& operator=(cxpair<T0, T1>&& other)
{ first = other.first; second = other.second; return *this; }
constexpr cxpair& operator=(const cxpair<T0, T1>& other)
{ first = other.first; second = other.second; return *this; }
T0 first;
T1 second;
};
template<typename Key, typename Value, std::size_t Size = 2>
struct map
{
using key_type = Key;
using mapped_type = Value;
using value_type = cxpair<Key, Value>;
constexpr map(std::initializer_list<value_type> list)
: map(list.begin(), list.end())
{ }
template<typename Itr>
constexpr map(Itr begin, const Itr &end)
{
std::size_t size = 0;
while (begin != end) {
if (size >= Size) {
throw std::range_error("Index past end of internal data size");
} else {
auto& v = data[size++];
v = std::move(*begin);
}
++begin;
}
}
// ... useful utility methods omitted
private:
std::array<value_type, Size> data;
// for the utilities, it makes sense to also have a size member, omitted for brevity
};
现在,如果你只是用普通的std::array
来做到这一点,那么开箱即用的东西就可以了:
constexpr std::array<cxpair<int, std::string_view>, 2> mapp = {{ {1, "1"}, {2, "2"} }};
// even with plain pair
constexpr std::array<std::pair<int, std::string_view>, 2> mapp = {{ {1, "1"}, {2, "2"} }};
不幸的是,我们必须显式给出数组的大小作为第二个模板参数。这正是我想避免的。 为此,我尝试构建您在那里看到的地图。 有了这个伙伴,我们可以写一些东西,例如:
constexpr map<int, std::string_view> mapq = { {1, "1"} };
constexpr map<int, std::string_view> mapq = { {1, "1"}, {2, "2"} };
不幸的是,一旦我们超过地图中的魔术常数Size
,我们就会得到一个错误,所以我们需要明确给出大小:
//// I want this to work without additional shenanigans:
//constexpr map<int, std::string_view> mapq = { {1, "1"}, {2, "2"}, {3, "3"} };
constexpr map<int, std::string_view, 3> mapq = { {1, "1"}, {2, "2"}, {3, "3"} };
当然,一旦你进入 constexpr 范围throw
,你就会得到一个编译错误,并且可以显式调整魔术常量。但是,这是我想隐藏的实现细节。用户不应该需要处理这些低级细节,这是编译器应该推断的东西。
不幸的是,我没有看到具有确切语法的解决方案map = { ... }
.我什至看不到像constexpr auto map = make_map({ ... });
这样的东西的光.此外,这是一个与运行时内容不同的 API,我想避免使用它以提高易用性。
那么,是否可以在编译时以某种方式从初始值设定项列表中推断出此大小参数?
std::array
有一个演绎指南:
template <class T, class... U>
array(T, U...) -> array<T, 1 + sizeof...(U)>;
这使您可以编写:
// ok, a is array<int, 4>
constexpr std::array a = {1, 2, 3, 4};
我们可以遵循相同的原则,为map
添加扣除指南,例如:
template <typename Key, typename Value, std::size_t Size>
struct map {
constexpr map(std::initializer_list<std::pair<Key const, Value>>) { }
};
template <class T, class... U>
map(T, U...) -> map<typename T::first_type, typename T::second_type, sizeof...(U)+1>;
这允许:
// ok, m is map<int, int, 3>
constexpr map m = {std::pair{1, 1}, std::pair{1, 2}, std::pair{2, 3}};
不幸的是,这种方法需要在初始值设定项列表中命名每种类型 - 即使你写了pair{1, 1}
,也不能只写{1, 2}
。
另一种方法是将右值数组作为参数:
template <typename Key, typename Value, std::size_t Size>
struct map {
constexpr map(std::pair<Key, Value>(&&)[Size]) { }
};
这避免了必须编写演绎指南,并让您只需要在第一个指南上编写类型,但代价是额外的一对大括号或参数:
// ok, n is map<int, int, 4>
constexpr map n{{std::pair{1, 1}, {1, 2}, {2, 3}, {3, 4}}};
// same
constexpr map n({std::pair{1, 1}, {1, 2}, {2, 3}, {3, 4}});
请注意,数组是pair<Key, Value>
的,而不是pair<Key const, Value>
的 - 这允许只写入pair{1, 1}
.由于您无论如何都要编写constexpr
地图,因此这种区别可能无关紧要。
@Barry的回答为我指明了正确的方向。总是在列表中显式列出pair
是不可取的。此外,我希望能够部分地专门化map
的模板参数列表。请考虑以下示例:
// for the sake of the example, suppose this works
constexpr map n({{1, "1"}, {2, "2"}});
// -> decltype(n) == map<int, const char*, 2>
// the following won't work
constexpr map<std::size_t, const char*> m({{1, "1"}, {2, "2"}});
但是,也许用户希望映射包含std::size_t
作为键,而键没有文字,即他/她必须定义一个用户定义的文字才能做到这一点。
我们可以通过将工作卸载到make_map
函数来解决此问题,从而允许我们对地图进行部分专用化:
// deduction guide for map's array constructor
template<class Key, class Value, std::size_t Size>
map(cxpair<Key, Value>(&&)[Size]) -> map<Key, Value, Size>;
// make_map builds the map
template<typename Key, typename Value, std::size_t Size>
constexpr auto make_map(cxpair<Key, Value>(&&m)[Size]) -> map<Key, Value, Size>
{ return map<Key, Value, Size>(std::begin(m), std::end(m)); }
// allowing us to do:
constexpr auto mapr = make_map<int, std::string_view>({ {1, "1"},
{2, "2"},
{3, "3"} });
- 具有initializer_list的编译时查找表
- 与Qt交叉编译到Raspberry Pi 3B+通讯录(协议缓冲区)-错误符号查找错误
- 查找编译时构造类的内存位置
- 编译库,以便 GDB 自动查找源
- 如何仅重新编译makefile中更改的内容,自动查找.cpp文件?
- 当其中一个函数未编译时,函数重载查找如何工作?
- 在编译时间创建查找表
- 使用STD集查找时,该程序将不会编译
- 如何查找Microsoft Visual C 中使用的编译预处理器
- 如何在编译时间检查查找表的大小正确
- 在编译发布时使用什么工具来查找c++错误.助推::阿西奥,cmake.vs2012.十字路口/0mq
- 使用CMake查找用CLang编译的Boost
- 在编译时查找基类
- GNU g++ 4.9.2 查找函数调用的编译错误
- 如何使用模板在编译时查找 2 个数字的 HCF
- 查找文件时出现Visual Studio编译错误
- 查找库是由SJLJ还是DWARF2编译器编译的
- 为什么我们不能在编译时查找指向的对象类型?
- 从可执行文件中查找编译优化标志
- 在visualstudio中查找编译时的C++平台目标