перегрузка функции rvalue - PullRequest
5 голосов
/ 16 июня 2011

Я хочу перегрузить функцию, чтобы она каким-то образом манипулировала своим аргументом и затем возвращала ссылку на аргумент - но если аргумент не является изменяемым, то он должен возвращать манипулированную копию объекта аргумент вместо. Поработав с ним целую вечность, вот что я придумала.

using namespace std;

string& foo(string &in)
{
    in.insert(0, "hello ");
    return in;
}

string foo(string &&in)
{
    return move(foo(in));
}

string foo(const string& in)
{
    return foo(string(in));
}

Кажется, этот код работает правильно, но мне интересно узнать, может ли кто-нибудь придумать лучший способ сделать это.

Вот тестовая программа:

int main(void)
{
    string var = "world";
    const string var2 = "const world";
    cout << foo(var) << endl;
    cout << var << endl;

    cout << foo(var2) << endl;
    cout << var2 << endl;

    cout << foo(var + " and " + var2) << endl;
    return 0;
}

Правильный вывод

hello world
hello world
hello const world
const world
hello hello world and const world

Я полагаю, было бы немного аккуратнее, если бы я мог сделать это:

string& foo(string &in)
{
    in.insert(0, "hello ");
    return in;
}

string foo(string in)
{
    return move(foo(in));
}

Конечно, это не работает, потому что большинство вызовов функций для foo были бы неоднозначными - включая вызов в самом foo! Но если бы я мог как-то сказать компилятору расставить приоритеты для первого ...

Как я уже сказал, код, который я разместил, работает правильно. Главное, что мне не нравится в этом, это повторяющийся дополнительный код. Если бы у меня было несколько таких функций, это превратилось бы в беспорядок, и большинство из них были бы очень повторяющимися. Итак, как вторая часть моего вопроса: кто-нибудь может придумать, как автоматически сгенерировать код для второй и третьей функций foo? например,

// implementation of magic_function_overload_generator
// ???

string& foo(string &in);
magic_function_overload_generator<foo>;

string& bar(string &in);
magic_function_overload_generator<bar>;

// etc

Ответы [ 5 ]

6 голосов
/ 16 июня 2011

Я бы избавился от ссылок все вместе и просто написал бы одну функцию, которая передает и возвращает значение:

std::string foo(std::string in)
{
    in.insert(0, "hello ");
    return in;
}

Если вы передаете lvalue, входная строка будет скопирована.Если вы передадите значение r, оно будет перемещено.

Когда вы выходите из функции, оптимизация именованного возвращаемого значения, вероятно, вступит в силу, так что возвращение в основном не работает.Если компилятор примет это решение, результат будет перемещен (даже если in является lvalue).

Хорошая вещь в ссылках на rvalue заключается в том, что вам нужно думать меньше овставлять ссылки в пользовательский код для повышения эффективности.С подвижными типами передача по значению практически так же эффективна, как и получается.

2 голосов
/ 16 июня 2011

Весь вопрос в том, почему вы хотите иметь такие перегрузки? Все эти перегрузки указывают на один интерфейс: foo (x). Но x parameter может быть параметром input или input/output в зависимости от его типа. Это очень, очень подвержено ошибкам. Пользователь должен выполнить дополнительную работу, чтобы убедиться, что его переменная не будет изменена. Никогда не делайте этого в производственном коде.

Я бы согласился с такими перегрузками:

string foo(string &&in);
string foo(const string& in);

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

Но почему вы хотите генерировать много таких перегрузок? Перегрузка && предназначена для оптимизации. Я бы сказал, очень деликатная оптимизация. Вы уверены, что вам это нужно во многих местах?

В любом случае, если вы действительно хотите генерировать код на C ++, шаблоны не очень хороший выбор. Я бы использовал какой-то внешний инструмент для этого. Лично я предпочитаю Cog .

1 голос
/ 16 июня 2011

А как насчет следующего простого подхода?

string& foo (string &change)  // this accepts mutable string
{
  change = string("hello ") + change;
  return change;
}

string foo (const string &unchange)  // this accepts not mutable string
{
  return string("hello ") + unchange;
}

См. Здесь вывод .

0 голосов
/ 17 июня 2011

Если вы не беспокоитесь об эффективности, вы можете сделать передачу по значению или передачу по константной ссылке, сделать копию и покончить с этим.

Тем не менее, если вы беспокоитесь об эффективности, я не думаю, что в этом ответе 1004 * лучше всего подойдет предложение о передаче по значению. Это потому, что я думаю, что это приводит к дополнительным копиям / перемещениям, поскольку NRVO, кажется, работает только с локальными переменными, а не с параметрами. Я думаю, что путь, который избегает ходов / копий в C ++ 0x, это двойные перегрузки, как показано в следующем коде:

#include <iostream>

struct A
{
  A() : i(0) {}
  A(const A& x) : i(x.i) { std::cout << "Copy" << std::endl; }
  A(A&& x) : i(x.i) { std::cout << "Move" << std::endl; }
  void inc() { ++i; }
  int i;
};

A f1(const A& x2) { A x = x2; x.inc(); return x; }
A&& f1(A&& x) { x.inc(); return std::move(x); }

A f2(A x) { x.inc(); return std::move(x); }

int main()
{
  A x;
  std::cout << "A a1 = f1(x);" << std::endl;
  A a1 = f1(x);
  std::cout << "A a2 = f1(A());" << std::endl;
  A a2 = f1(A());
  std::cout << "A b1 = f2(x);" << std::endl;
  A b1 = f2(x);
  std::cout << "A b2 = f2(A());" << std::endl;
  A b2 = f2(A());
  std::cout << std::endl;
  std::cout << "A a3 = f1(f1(x));" << std::endl;
  A a3 = f1(f1(x));
  std::cout << "A a4 = f1(f1(A()));" << std::endl;
  A a4 = f1(f1(A()));
  std::cout << "A b3 = f2(f2(x));" << std::endl;
  A b3 = f2(f2(x));
  std::cout << "A b4 = f2(f2(A()));" << std::endl;
  A b4 = f2(f2(A()));
  std::cout << std::endl;
  std::cout << "A a5 = f1(f1(f1(x)));" << std::endl;
  A a5 = f1(f1(f1(x)));
  std::cout << "A a6 = f1(f1(f1(A())));" << std::endl;
  A a6 = f1(f1(f1(A())));
  std::cout << "A b5 = f2(f2(f2(x)));" << std::endl;
  A b5 = f2(f2(f2(x)));
  std::cout << "A b6 = f2(f2(f2(A())));" << std::endl;
  A b6 = f2(f2(f2(A())));
}

Который дает следующие результаты:

A a1 = f1(x);
Copy
A a2 = f1(A());
Move
A b1 = f2(x);
Copy
Move
A b2 = f2(A());
Move

A a3 = f1(f1(x));
Copy
Move
A a4 = f1(f1(A()));
Move
A b3 = f2(f2(x));
Copy
Move
Move
A b4 = f2(f2(A()));
Move
Move

A a5 = f1(f1(f1(x)));
Copy
Move
A a6 = f1(f1(f1(A())));
Move
A b5 = f2(f2(f2(x)));
Copy
Move
Move
Move
A b6 = f2(f2(f2(A())));
Move
Move
Move

Возможно, вам удастся выполнить несколько шаблонных приемов, чтобы избежать записи нескольких перегрузок, например:

template <class T>
param_return_type<T&&>::type f3(T&& y, typename std::enable_if<...>::type* dummy = 0 ) 
{ 
  typedef return_t param_return_type<T&&>::type;
  return_t x = static_cast<return_t>(y);
  x.inc();
  return static_cast<return_t>(x);
}

Где param_return_type<T>::type - T при прохождении (const) T& и T&& при прохождении T&&. std::enable_if<...> вы можете использовать, если хотите, чтобы этот шаблон принимал определенные параметры.

Я не был уверен, как написать определение param_return_type<T>::type, так как кажется, что std::remove_lvalue_reference нет. Если кто-нибудь знает, как это сделать, не стесняйтесь редактировать / добавлять в мой пост.

0 голосов
/ 16 июня 2011

В том же ключе, что и ответ @ iammilind, но без дублирования:

#include <iostream>
using namespace std;

string foo(const string &unchange) {
  return string("hello ") + unchange;
}

string& foo(string &change) {
  return change = foo(static_cast<const string&>(foo));
}

int main(int argc, char** argv) {
    string a = "world";
    const string b = "immutable world";
    cout << foo(a) << '\n' << foo(b) << '\n';
    cout << foo(a) << '\n' << foo(b) << '\n';
}

Примечание: вы также можете использовать const_cast здесь, чтобы добавить квалификацию const.

...