Можно ли создать std::map как rvalue?

Я не уверен, является ли это ошибкой моего синтаксиса C++ или это что-то, что вообще невозможно выполнить.

Я хочу определить класс, который принимает std::map в качестве аргумента конструктора. Затем я хочу создать экземпляр этого класса, передав «временный» (уместно называть это «rvalue»?) std::map. т.е. Я не хочу создавать lvalue std::map, а затем передавать его конструктору.

Можно ли это осуществить? Я пробовал следующее (закомментированные строки показывают неудачные попытки)

#include <map>
#include <string>
#include <iostream>

class Test
{
    public:
        Test(std::map<int,char>& rMap)
        {
            std::map<int,char>::iterator iter;

            for (iter = rMap.begin(); iter != rMap.end(); ++iter)
            {
                mMap[iter->first] = mMap[iter->second];
            }
        }
        virtual ~Test(){}

    protected:
        std::map<int, char> mMap;
};

int main()
{
    std::cout << "Hello world!" << std::endl;

    //Test test({1,'a'});                   // Compile error.
    //Test test(std::map<int,char>(1,'a')); // Also compile error.
    //Test test(std::map<int,char>{1,'a'}); // Yet again compile error.

    return 0;
}

Это мой компилятор:

g++ (GCC) 4.4.7 20120313 (Red Hat 4.4.7-11)

Ошибки компиляции могут быть опубликованы по запросу, но я не уверен, что они будут полезны, если моя проблема синтаксическая.

Спасибо.


person StoneThrow    schedule 26.07.2016    source источник
comment
Вы пробовали Test test(std::map<int, char>{{1, 'a'}}); с двумя наборами фигурных скобок?   -  person alter igel    schedule 27.07.2016
comment
@TimStraubinger - попробовал это по вашему предложению, но получил еще одну ошибку компиляции.   -  person StoneThrow    schedule 27.07.2016
comment
Какая ошибка компиляции?   -  person Lightness Races in Orbit    schedule 27.07.2016
comment
Обновлен вопрос с конструктором, принимающим ссылку на константу, а также добавлены ошибки компиляции из каждой из моих трех неудачных попыток создать тест.   -  person StoneThrow    schedule 27.07.2016
comment
Обратите внимание, что все ваши ошибки компиляции указывают на одну и ту же строку. Ошибки не имеют ничего общего с rvalues ​​- вы пытаетесь назначить константный итератор неконстантному итератору.   -  person Barry    schedule 27.07.2016
comment
Я отменил ваше редактирование, поскольку все 4 ответа касаются исходной версии вопроса, а новый вопрос имеет совершенно не связанную проблему.   -  person Barry    schedule 27.07.2016


Ответы (4)


#include <map>

class Test {
public:
    Test(std::map<int,char> const& cMap)
    {
        std::map<int,char>::const_iterator iter;
        for (iter = cMap.cbegin(); iter != cMap.cend(); ++iter)
        {
            mMap[iter->first] = mMap[iter->second];
        }
    }
    virtual ~Test() { }
protected:
    std::map<int, char> mMap;
};

int main() {
    Test test(std::map<int,char>({{1,'a'}, {2, 'b'}}));
    return 0;
}

Некоторые пояснения:

  1. Вам нужен конструктор Test(T), Test(const T&) или Test(T&&), если вы хотите передать в него временный объект (например, std::map<int,char>()). Не забывайте, для чего используются конструкторы T&&. Если сомневаетесь, не используйте их.
  2. Если вы работаете с const std::vector/map/list/..., вы не можете использовать .begin() и .end() для перебора элементов — используйте .cbegin() и .cend(). Более того, с auto это станет проще. Просто попробуй

    for (auto iter = rMap.cbegin(); iter != rMap.cend(); ++iter)
    

    вместо

    std::map<int,char>::const_iterator iter;
    for (iter = rMap.cbegin(); iter != rMap.cend(); ++iter)
    
  3. Чтобы инициализировать std::map, используйте инициализаторы с двойными скобками, такие как {{1,'a'}, {2, 'b'}} -- {{key, value}, {second_key, second_value}, ...}, эта конструкция доступна, потому что

    1) std::map имеет этот конструктор:

    map( std::initializer_list<value_type> init,
         const Compare& comp = Compare(),
         const Allocator& alloc = Allocator() );
    // see http://en.cppreference.com/w/cpp/container/map/map
    

    2) внутренние фигурные скобки {1,'a'} интерпретируются как вызов конструктора value_type. А value_type вместо std::map<int, char> равно std::pair<int, char>.

P. S. не забывайте, что std::map<int,char>() и std::map<int,char>{} вызовы конструктора равны. Это означает, что вы можете опустить передние скобки: std::map<int,char>({{1,'a'},{2,'b'}}) --> std::map<int,char>{{1,'a'},{2,'b'}}

person Vyacheslav Napadovsky    schedule 26.07.2016
comment
Мне нравится, как твоя проза и твой пример говорят о почти противоположных вещах. - person Lightness Races in Orbit; 27.07.2016
comment
@LightnessRacesinOrbit хорошо, автор вопроса хотел принять значение r в конструкторе. Мой пример решает вопрос автора, но мне все еще интересно, понимает ли автор, где должны использоваться r-значения. - person Vyacheslav Napadovsky; 27.07.2016
comment
Могли бы вы сказать, что этот ответ, вероятно, увеличит это понимание? - person Lightness Races in Orbit; 27.07.2016
comment
@LightnessRacesinOrbit Ладно. Хорошо. Не принимайте близко к сердцу. Я исправил свой ответ. - person Vyacheslav Napadovsky; 27.07.2016
comment
@LightnessRacesinOrbit Я не думаю, что нужны какие-либо комментарии, потому что код показывает сам за себя. const& -- ссылка на константу, const_iterator cbegin cend -- работа с const& и правильное построение карты с двойными скобками {{1,'a'}, {2, 'b'}} - person Vyacheslav Napadovsky; 27.07.2016
comment
@slavanap Ваш метод создания тестового объекта сработал для меня. Чтобы ответить на ваш запрос, я понимаю концепцию rvalues. Это было частично академическим упражнением для меня, чтобы понять создание rvalue std::map с расширенными списками инициализаторов, как это сделали вы. Списки-инициализаторы достаточно просты с POD и простыми классами, но я думаю, что запутался в том, куда идут круглые скобки и куда идут фигурные скобки при создании std::map таким образом. Я рассмотрю ваше решение, но я был бы рад, если бы у вас было и текстовое объяснение. - person StoneThrow; 27.07.2016
comment
Объясните, почему это необходимо/лучше/рекомендуется. ОП не стал бы спрашивать, знали ли они уже; то же самое касается будущих посетителей. - person Lightness Races in Orbit; 27.07.2016
comment
@StoneThrow, если вы думаете, что понимаете r-значения, попробуйте понять разницу между std::move и std::forward и что такое универсальная ссылка. Я потратил 2 часа на обсуждение на канале IRC, чтобы получить эту крошечную разницу. Это не так просто, поверьте! :D - person Vyacheslav Napadovsky; 27.07.2016
comment
@LightnessRacesinOrbit добавил некоторые пояснения - person Vyacheslav Napadovsky; 27.07.2016
comment
@slavanap Я бы хотел, чтобы вы внесли изменения с комментариями. Это хороший стиль программирования, и в конце концов вы здесь, чтобы помочь - person strangeqargo; 27.07.2016
comment
@LightnessRacesinOrbit #TIL to discombobulate, спасибо - person strangeqargo; 27.07.2016
comment
Теперь это ответ. - person Lightness Races in Orbit; 27.07.2016
comment
Что касается № 2, конечно, вы можете, вы просто получите const_iterator, а не iterator. Прочтите документацию - person Lightness Races in Orbit; 27.07.2016
comment
и в вашем PS есть непоследовательность - person Lightness Races in Orbit; 27.07.2016
comment
Очень полезный ответ и обсуждение. Спасибо slavanap и легкостьracesinorbin - person StoneThrow; 27.07.2016
comment
std::map имеет конструктор копирования. Вы должны использовать его вместо того, чтобы добавлять элементы один за другим. - person Barry; 27.07.2016
comment
@ Барри, главный вопрос был не об этом. - person Vyacheslav Napadovsky; 27.07.2016
comment
@slavanap А? Я имею в виду конструктор, который вы написали в Test. Просто Test(std::map<int,char> const& cMap) : mMap(cMap) { } - person Barry; 27.07.2016
comment
@ Барри, я только что скопировал код операции. В этом нет ничего плохого. Перечитайте вопрос ОП. Ваша точка зрения имеет место быть, но она не имеет отношения к вопросу. - person Vyacheslav Napadovsky; 27.07.2016

Do

Test(std::map<int, char> rMap) : mMap(std::move(rMap)) {}

or

Test(std::map<int, char>&& rMap) : mMap(std::move(rMap)) {}

or

Test(const std::map<int, char>& rMap) : mMap(rMap) {}

Temporary не может привязываться к неконстантной ссылке l-value.

И использовать его как

Test test({{1,'a'}});
Test test2({{1,'a'}, {2, 'b'}});
person Jarod42    schedule 26.07.2016
comment
Хорошо. Почему? Бесполезно без объяснения причин. Не просто дамп кода. - person Lightness Races in Orbit; 27.07.2016
comment
@Jarod42 Jarod42 Если бы я выбрал вариант 1, как правильно создать экземпляр тестового объекта? У меня все еще возникают проблемы с синтаксисом создания rvalue std::map. - person StoneThrow; 27.07.2016

Да, но ваш конструктор принимает ссылку lvalue. Вместо этого это должна быть ссылка-на-const или ссылка на rvalue.

Так же, как и с любым другим типом.

person Lightness Races in Orbit    schedule 26.07.2016
comment
Если я хочу использовать ссылку на const (в отличие от ссылки на rvalue), каков правильный синтаксис? После определения конструктора Test(std::map‹int,char› const& rMap) я все еще пытаюсь вызвать его с экземпляром rvalue std::map. - person StoneThrow; 27.07.2016
comment
@StoneThrow: Что означает на самом деле, что я борюсь? - person Lightness Races in Orbit; 27.07.2016
comment
борьба здесь конкретно означает продолжающиеся ошибки компиляции. Изменив конструктор на Test(std::map‹int,char› const& rMap), я по-прежнему не могу создать экземпляр объекта Test, используя любой из трех методов в моем main(). Каков правильный синтаксис для создания тестового объекта, если его конструктор использует ссылку на константу? - person StoneThrow; 27.07.2016
comment
В качестве вторичного примечания: если эту проблему нельзя решить только с помощью ссылки rvalue, я хотел бы решить ее с помощью предложенной вами ссылки на константу. - person StoneThrow; 27.07.2016
comment
@StoneThrow: Пока вы не сообщите нам, что это за ошибки компиляции и как выглядит ваш код, я не понимаю, как вы ожидаете, что мы поможем вам их исправить. Я борюсь, и постоянные ошибки компиляции не помогают нам помочь вам. - person Lightness Races in Orbit; 27.07.2016
comment
Обновлен вопрос с конструктором, принимающим ссылку на константу, а также добавлены ошибки компиляции из каждой из моих трех неудачных попыток создать тест. Спасибо за вашу настойчивость в расследовании этого вместе со мной. - person StoneThrow; 27.07.2016
comment
@StoneThrow вы неправильно создаете std::map для аргумента своего класса. Обратите внимание на разницу: ваш код Test test(std::map<int,char>(1,'a')); правильный код Test test(std::map<int,char>({{1,'a'}, {2,'b'}})); ‹-- двойные скобки. - person Vyacheslav Napadovsky; 27.07.2016

Вы должны использовать:

class Test
{
public:
    Test(const std::map<int,char>& rMap) : mMap(rMap)
    {}
    Test(std::map<int,char>&& rMap) : mMap(std::move(rMap))
    {}
    virtual ~Test(){}
protected:
    std::map<int, char> mMap;
};

Это предоставляет два конструктора: один, который принимает ссылку const lvalue и копирует ее содержимое в mMap, и другой, который принимает ссылку rvalue и перемещает ее содержимое в mMap. Это позволяет вам создать экземпляр Test, используя либо именованный std::map, либо временный:

int main()
{
    std::map<int,char> m{{1, 'a'}, {2,'b'}};
    Test t1{m};
    Test t2{{{1, 'a'}, {2, 'b'}}};
}

Это позволяет избежать ненужных копий, когда это возможно, но все же позволяет сделать копию map, когда это необходимо.

person Miles Budnek    schedule 26.07.2016
comment
Конечно, способ сделать это — это одна перегрузка, которая берет карту по значению? - person Lightness Races in Orbit; 27.07.2016
comment
@LightnessRacesinOrbit Люди (например, Ниблер) так говорят, но было бы здорово, если бы это не требовало дополнительного хода. - person Barry; 27.07.2016
comment
@Barry: Ход фактически бесплатный, даже если его нельзя оптимизировать (что бывает редко). Гораздо лучше, чем повторяться, если только у вас нет обстоятельств, при которых это действительно является проблемой. - person Lightness Races in Orbit; 28.07.2016