简体   繁体   中英

implicit std::pair construction in variadic template construction

I have a constexpr key-value map that roughly has this definition:

// map with `pos` remaining entries
template<typename K, typename V, size_t pos>
class Map {
public:
    template<class Head, class... Tail>
    constexpr Map(Head head, Tail... tail)
        :
        value{head},
        tail{tail...} {}

    Element<K, V> value;
    const Map<K, V, pos - 1> tail;

    // members etc
};

// map end element.
template<typename K, typename V>
class Map<K, V, 0> {
public:
    constexpr Map() {}

    // end element specifics.
};

To initialize a key-value map at compile time, I have a utility function that forwards the elements:

template<typename K, typename V, typename... Entries>
constexpr Map<K, V, sizeof...(Entries)> create_const_map(Entries&&... entry) {
    return Map<K, V, sizeof...(entry)>(Entry<K, V>{std::forward<Entries>(entry)}...);
}

The Element is defined as:

template<class K, class V>
class Entry {
public:
    constexpr Entry(const K &key, const V &value)
        :
        key{key},
        value{value} {}

    constexpr Entry(std::pair<K, V> pair)
        :
        key{pair.first},
        value{pair.second} {}

    const K key;
    const V value;
};

To actually create a map, I can use std::make_pair successfully:

constexpr auto cmap = create_const_map<int, int>(
    std::make_pair(0, 0),
    std::make_pair(13, 37),
    std::make_pair(42, 9001)
);

What I want now is to eliminate the calls to std::make_pair and use braces instead:

constexpr auto cmap = create_const_map<int, int>(
    {0, 0},
    {13, 37},
    {42, 9001}
);

But this leads to compilation failure as the {} are not deduced as construction for pairs:

/home/jj/devel/openage/libopenage/datastructure/tests.cpp:191:24: error: no matching function for call to 'create_const_map'
        constexpr auto cmap = create_const_map<int, int>(
                              ^~~~~~~~~~~~~~~~~~~~~~~~~~
/home/jj/devel/openage/libopenage/datastructure/constexpr_map.h:286:46: note: candidate function not viable: requires 0 arguments, but 3 were provided
constexpr Map<K, V, sizeof...(Entries)> create_const_map(Entries&&... entry) {
                                        ^

What can I do so that the {a, b} can be used instead of std::make_pair(a, b) ?

This is not easily possible because as @Rostislav says, if the argument is a braces init list, the compiler cannot deduce a type for it.

I worked it around by manually creating a sufficiently large number of overloaded functions. In the following, the overloads are the function call operators ( operator() ) and create_map is a variable template of that function object. You pass the number of overloads to create to Overload . My C++ is rough, so perhaps this is not ideal, but it seems to work.

template<typename D, int N, typename ...E>
struct OverloadImpl;

template<typename D, int N, typename E, typename ...Es>
struct OverloadImpl<D, N, E, Es...> : OverloadImpl<D, N-1, E, E, Es...> {
    decltype(auto) operator()(const E& e, const Es&... es) {
       return static_cast<D&>(*this).exec(e, es...);
    }
    using OverloadImpl<D, N-1, E, E, Es...>::operator();
};

template<typename D, typename E, typename ...Es>
struct OverloadImpl<D, 0, E, Es...> { 
    decltype(auto) operator()() { 
       return static_cast<D&>(*this).exec();   
    }    
};

template<typename D, typename E, int N>
struct Overload : OverloadImpl<D, N, E>
{ };

To apply the above to your case, you can derive from Overload and provide an exec function.

template<typename K, typename V, int C>
struct M {
   template<typename ...T>
   M(T&&...) { }
};

template<typename K, typename V>
struct Entry {
   Entry(K, V) { }    
};

template<typename K, typename V>
struct CreateMapImpl : Overload<CreateMapImpl<K, V>, Entry<K, V>, 10> {
    template<typename ...T>
    M<K, V, sizeof...(T)> exec(const T&... t) {
       return { t... };   
    }
};

template<typename K, typename V>
CreateMapImpl<K, V> create_map{};

int main() {
   create_map<int, std::string>({1, "one"}, {2, "two"}, {3, "three"});   
}

It seems to me that it's not possible to do what you need. The compiler has to deduce the types for the Entries parameter pack. However, the braced initializers that you want to pass in have no type. From cppreference , Notes section:

A braced-init-list is not an expression and therefore has no type, eg decltype({1,2}) is ill-formed. Having no type implies that template type deduction cannot deduce a type that matches a braced-init-list, so given the declaration template<class T> void f(T); the expression f({1,2,3}) is ill-formed.

That's why gcc (according to cpp.sh ) says that Entries = {} :

63:1: error: too many arguments to function 'constexpr Map create_const_map(Entries&& ...) [with K = int; V = int; Entries = {}]'

I guess if you want a more concise way to initialize your Map, you'd need to make a short-hand notation for the std::make_pair .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM