1

I'm trying to figure out how exactly the unique_ptr type plays with templates in C++11, and am not having much luck: Specifically, I'm trying to make a template function which inserts a unique_ptr value for a given unique key in a given map if there is not already a value for the key in the map, and moves ownership of the value pointed to by the unique_ptr to the value in the map; If the key is already present, a runtime error is thrown.

Now, moving the value of a unique_ptr is quite straightforward if you don't have to pass it to a function:

#include <iostream>
#include <memory>
#include <unordered_map>

using namespace std;

int main(int argc, char *argv[])
{
    unordered_map<char, unique_ptr<int> > testMap;

    char key1 = 'A';
    unique_ptr<int> val1(new int(1));
    testMap[key1] = move(val1);

    // Print the results
    cout << "testMap[" << key1 << "] = " << *testMap[key1] << endl;

    return 0;

}

Non-template function

Passing the unique_ptr to a function is a bit more complicated:

#include <iostream>
#include <memory>
#include <unordered_map>

using namespace std;

void moveValueForUniqueKey(unordered_map<char, unique_ptr<int> >& targetMap, char key, unique_ptr<int> value) throw(char)
{
    // Check if the key is already in the map
    auto it = targetMap.find(key);
    if (it != targetMap.end())
    {
        throw key;
    }
    else
    {
        targetMap[key] = move(value);
    }
}

int main(int argc, char *argv[])
{
    unordered_map<char, unique_ptr<int> > testMap;
    char key1 = 'A';
    unique_ptr<int> val1(new int(1));

    // Try inserting the first key-value pair
    try
    {
        moveValueForUniqueKey(testMap, key1, move(val1));
    }
    catch (char& duplicateKey)
    {
        cerr << "Key '" << duplicateKey << "' already in map." << endl;
    }

    // Print the key-value pairs
    for (pair<const char, unique_ptr<int> >& entry : testMap)
    {
        cout << "testMap['" << entry.first << "'] = " << *entry.second << endl;
    }

    unique_ptr<int> val2(new int(2));

    // Try inserting the key again
    try
    {
        moveValueForUniqueKey(testMap, key1, move(val2));
    }
    catch (char& duplicateKey)
    {
        cerr << "Key '" << duplicateKey << "' already in map." << endl;
    }

    // Print the key-value pairs again
    for (pair<const char, unique_ptr<int> >& entry : testMap)
    {
        cout << "testMap['" << entry.first << "'] = " << *entry.second << endl;
    }

    return 0;

}

This code outputs:

testMap['A'] = 1
Key 'A' already in map.
testMap['A'] = 1

Function template

Now, when I try to make a template of this function, replacing the previous declaration/implementation of moveValueForUniqueKey(...) with:

template<typename K, typename V, typename M>
void moveValueForUniqueKey(M targetMap, K key, unique_ptr<V> value) throw(char)
{
    // Check if the key is already in the map
    auto it = targetMap.find(key);
    if (it != targetMap.end())
    {
        throw key;
    }
    else
    {
        targetMap[key] = move(value);
    }
}

// Instantiate template function
template
void moveValueForUniqueKey(unordered_map<char, unique_ptr<int> >& targetMap, char key, unique_ptr<int> value) throw(char);

I simply get the compiler error use of deleted function ‘constexpr std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2>&) [with _T1 = const char, _T2 = std::unique_ptr<int>, std::pair<_T1, _T2> = std::pair<const char, std::unique_ptr<int> >]’.

What exactly is going on here, and how can you accomplish what I'm trying here (pass unique_ptr objects into template functions using move semantics)?

errantlinguist
  • 3,658
  • 4
  • 18
  • 41

2 Answers2

1

First of all, this:

remove_reference<unique_ptr<int>&>::type

Is just equivalent to this:

unique_ptr<int>

I do not see the need for the remove_reference there. To convince yourself, try the following:

#include <type_traits>
#include <memory>

static_assert(std::is_same<
    remove_reference<unique_ptr<int>&>::type, 
    unique_ptr<int>>::value, "!");

And you will see that the assertion does not fire (live example).

Most likely the reason for the compiler error you are getting is that you are not using the typename disambiguator in your function template:

template<typename K, typename V, typename M>
void moveValueForUniqueKey(M targetMap, K key, 
    typename remove_reference<unique_ptr<V>&>::type value) throw(char)
//  ^^^^^^^^
{
    // ...
}

But again, there is no reason to use remove_reference there:

template<typename K, typename V, typename M>
void moveValueForUniqueKey(M targetMap, K key, unique_ptr<V> value) throw(char)
//                                             ^^^^^^^^^^^^^
{
    // ...
}

Finally, keep in mind that dynamic exception specifications are deprecated in C++11.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Oh, and thanks for the heads-up about the exception specifications: Somehow that never got expressed loudly enough for me to take notice. – errantlinguist Jun 05 '13 at 22:17
1

Well first you would need typename since you are obtaining a type through a template argument:

template<typename K, typename V, typename M>
void moveValueForUniqueKey(
    M targetMap, K key, typename remove_reference<unique_ptr<V>&>::type value) throw(char)
    //                  ^^^^^^^^
{...}

But you don't need the signature remove_refrence<T&>::type. Why aren't you just using T?

template<typename K, typename V, typename M>
void moveValueForUniqueKey(M targetMap, K key, unique_ptr<V> value) throw(char)
{...}

std::unique_ptr cannot be copied, so you should simply move any lvalues into the parameter. For example, you have an invalid line in your main. Change this:

moveValueForUniqueKey(testMap, key1, move(val2));

to this

moveValueForUniqueKey(move(testMap), key1, move(val2));
//                    ^^^^^^^^^^^^^
David G
  • 94,763
  • 41
  • 167
  • 253
  • Thanks; the `remove_reference::type` nonsense means that I should probably get some sleep now... – errantlinguist Jun 05 '13 at 21:56
  • Just to note: `void moveValueForUniqueKey(M targetMap, K key, unique_ptr value) throw(char)` doesn't compile on my machine: `use of deleted function ‘constexpr std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2>&) [with _T1 = const char, _T2 = std::unique_ptr, std::pair<_T1, _T2> = std::pair >]` – errantlinguist Jun 05 '13 at 22:01
  • Yes: it is used internally by `unordered_map`. – errantlinguist Jun 05 '13 at 22:06
  • @errantlinguist `unique_ptr` is not copyable, so somewhere in your code you are doing copy-construction. – David G Jun 05 '13 at 22:07
  • @errantlinguist I found it. The offending line is: `moveValueForUniqueKey(testMap, key1, move(val2));`. You must move the parameter: `moveValueForUniqueKey(move(testMap), key1, move(val2));` – David G Jun 05 '13 at 22:08
  • Yes, hence the attempt to pass `unique_ptr`, `unique_ptr&`, `remove_reference&>::type` and `remove_reference&>::type&`, none of which work. – errantlinguist Jun 05 '13 at 22:09
  • @errantlinguist You forgot to move the first parameter: `moveValueForUniqueKey(std::move(testMap), key1, std::move(val2));` – David G Jun 05 '13 at 22:11
  • But why, when I pass a reference to an `unordered_map >`, do the map entries get copied??? I'm even more confused now... – errantlinguist Jun 05 '13 at 22:12
  • @errantlinguist The map entries do **not** get copied if you pass by reference. I was just assuming that you didn't want to pass by reference since you were using `remove_reference::type`. If you want you can change the signature in your function to `void moveValueForUniqueKey(M& targetMap, K key, unique_ptr& value)` where you pass both the `unique_ptr` and `targetMap` by reference. With that you won't need to use `std::move` when you call. – David G Jun 05 '13 at 22:14
  • @errantlinguist Okay, and here is a [live demo](http://coliru.stacked-crooked.com/view?id=0b3a5d7f59c0fc774f47061dbb186713-41475e255c735859f2b181fd0d057a6f) of your program. The explicit-instantiation of your function template was also causing problems, but I don't know why. – David G Jun 05 '13 at 22:18