带有initializer_list

时间:2019-12-03 11:12:44

标签: c++ c++17

假设您有一些哈希值,并且想在编译时将它们映射到它们各自的字符串。 理想情况下,我希望能够按照以下方式写点东西:

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({ ... });之类的东西。此外,这是与runtime-stuff不同的API,我希望避免增加易用性。

那么,有可能在编译时从初始化列表中推断出此size参数吗?

2 个答案:

答案 0 :(得分:3)

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

不幸的是,这种方法需要命名初始值设定项列表中的每种类型-即使编写了{1, 2},也不能只写pair{1, 1}


另一种方法是将右值数组作为参数:

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映射,因此这种区别可能无关紧要。

答案 1 :(得分:0)

@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"} });