Шаблонная функция C ++ variadi c с семантикой перемещения - PullRequest
3 голосов
/ 21 марта 2020

В C ++ у меня есть набор шаблонных функций variadi c, которые я хотел бы принять произвольное количество параметров, либо ссылки на константы, либо в качестве ссылок r-значения (поэтому я могу перемещать вещи, а не копировать, когда это возможно ). Однако я обнаружил, что версия, которая принимает ссылку на константу, вызывается, даже когда я оборачиваю аргументы в std::move().

// Accept end of parameters.
void example () {}

// Accept a non-constant R-value reference to do a move
template <typename... More>
void example (std::string &&value, More... parameters) {
    std::cout << value << ": moved" << std::endl;
    example (parameters...);
}

// Accept a constant reference parameter.    
template <typename... More>
void example (const std::string &value, More... parameters) {
    std::cout << value << ": copied" << std::endl;
    example (parameters...);
}

int main (int, char **) {
    std::string first { "first" };
    std::string second { "second" };
    std::string third { "third" };

    std::cout << "Trying variadic with move as second parameter: " << std::endl;
    example (first, std::move (second), third);
    // std::cout << "Trying variadic with move as first parameter: " << std::endl;
    // This next line won't even compile when uncommented
    // example (std::move (first), std::move (second), third);
    return 0;
}

Вывод:

Trying variadic with move as second parameter: 
first: copied
second: copied
third: copied

вместо ожидаемое:

Trying variadic with move as second parameter: 
first: copied
second: moved
third: copied

И в качестве бонуса, когда я оборачиваю первый аргумент в std::move(), я получаю ошибку компиляции на g ++ 7 и clang 9.

Что я делаю не так?

Ответы [ 3 ]

3 голосов
/ 21 марта 2020

Здесь есть несколько проблем:

  • More... parameters всегда получает аргументы по значению (пока выводятся параметры шаблона), потому что типы в typename ...More никогда не будут выведены в качестве ссылок.

  • Все аргументы, переданные example в example(parameters...);, всегда будут lvalues.

  • Перегрузка string && может ' t const string &, потому что он еще не объявлен в этот момент.

Вместо передачи по значению, вы должны использовать пересылочные ссылки и std::forward. И вам нужно объявить перегрузку const string &, прежде чем определять string &&.

void example() {}

// Declare lvalue overload.
template <typename ...More>
void example(const std::string &value, More &&... parameters);

// Rvalue overload.
template <typename... More>
void example (std::string &&value, More &&... parameters) {
    std::cout << value << ": moved" << std::endl;
    example(std::forward<More>(parameters)...);
}

// Lvalue overload.
template <typename ...More>
void example(const std::string &value, More &&... parameters) {
    std::cout << value << ": copied" << std::endl;
    example(std::forward<More>(parameters)...);
}
1 голос
/ 21 марта 2020

Это работает:

void  example2 () {}

template<typename First, typename... More>
void  example2( First &&value, More&&... parameters ) {
    if constexpr( std::is_rvalue_reference_v<decltype(value)> ) {
        std::cout << value << ": moved" << std::endl;
    }
    else {
        std::cout << value << ": copied" << std::endl;
    }
    example2( std::forward<More>(parameters)... );
}

int main (int, char **) {
    std::string first  { "first" };
    std::string second { "second" };
    std::string third  { "third" };

    std::cout << "copy, move, copy: " << std::endl;
    example2( first, std::move(second), third );

    // if we really moved second in the previous call
    // then second will be an empty string here.
    std::cout << "move, move, copy: " << std::endl;
    example2(std::move(first), std::move(second), third);

    return 0;
}

Если вы не отправляете параметры, любой && будет передан как &. Это основная проблема с вашей попыткой.

Вам также не нужны две функции для обработки значения, если вы сделаете его параметром && шаблона, он будет обрабатывать оба случая: lvalue и rvalue, которые мы можем определить с помощью is_rvalue_reference <>.

0 голосов
/ 21 марта 2020

HolyBlackCat уже упоминал, почему ваш код ведет себя так, как он делает, поэтому я не буду объяснять это здесь снова. Вот еще одно решение с использованием выражений сгиба:

template <typename T>
void process(T const& lvalue)
{
    std::cout << lvalue << ": copied\n";
}

// the enable_if is necessary because T&& is a forwarding reference, not a rvalue reference
template <typename T, typename = std::enable_if_t<!std::is_lvalue_reference_v<T>>>
void process(T&& rvalue)
{
    std::cout << rvalue << ": moved\n";
}

template <typename... Args>
void example(Args&&... args)
{
    (process(std::forward<Args>(args)), ...);    
}

Пример

...