2011-06-16 2 views
5

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

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 
+7

Это звучит страшно. В зависимости от того, какой тип я передаю функции, результирующее состояние возвращаемого значения * и параметра * может быть совершенно иным. Это просто требует тонких ошибок. Почему бы не позволить пользователю решить, хочет ли он изменить объект на месте или вернуть копию, явно называя разные функции? – jalf

+0

Мне это не кажется особенно страшным, но, возможно, ты прав. То, как я думаю об этом, заключается в том, что функция меняет ввод всякий раз, когда это возможно; но если он не может ... тогда это не так, но он все равно дает правильное возвращаемое значение. То, что я могу использовать для него, - это что-то вроде функции «Пунктуация», которая берет плохо перемежаемую строку и исправляет ее. Возможно, вы захотите отправить результат непосредственно в cout, иначе вы можете выполнить некоторые другие операции над строкой. Поэтому иногда вы можете передавать постоянные значения, а иногда ... ну, вы поняли эту идею. – karadoc

+1

, но моя точка зрения заключается в том, что независимо от того, что происходит или нет, зависит не от того, чего хочет программист, а от некоторых относительно тонких семантических деталей (это тип аргумента const или нет? Является ли это rvalue или нет?), который со временем может легко измениться без того, чтобы программист явно принял решение о том, что «теперь я хочу вернуть копию вместо изменения объекта на месте». Я понимаю, что вы пытаетесь сделать, но это решение, которое программист может легко сделать, и когда у вашей библиотеки неправильное предположение может иметь потенциально очень плохие последствия. – jalf

ответ

4

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

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

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

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

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

+0

В основном это то, что я сделал. Жальф и другие, убедили меня в том, что я полностью определил все это. Одной функции не нужно делать как изменяемые, так и неизменные версии - и, вероятно, это более запутанно, если одна функция выполняет обе эти вещи. Наверное, я переусердствовал из-за идеи использовать ссылки rvalue, чтобы сделать что-то классное. Я бы хотел, чтобы я никогда не слышал о ссылках на rvalue, тогда я бы выбрал это решение в первую очередь и не потратил впустую полдня, пытаясь что-то сделать! – karadoc

+0

Не волнуйтесь, классный материал все еще случается - под капотом;) – fredoverflow

1

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

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; 
} 

Просмотреть output here.

+0

Плохая вещь об этом подходе заключается в том, что тело функции должно быть записано дважды. В этом конкретном примере это лучше, чем у меня, но если foo является чем-то длинным и сложным, тогда это удвоит требуемый код. – karadoc

+2

@karadoc: вы можете легко написать не мутирующий в терминах мутирующего. 'int notModifying (const int & i) {int ii = i; возвращение модификации (ii); } ' –

0

В том же ключе, что и @ 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'; 
} 

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

2

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

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

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

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

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

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

0

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

Однако, если вы беспокоитесь об эффективности, я не думаю, что предложение по значению в этом reply - лучший подход. Это связано с тем, что я думаю, что это приводит к дополнительным копиям/перемещениям, поскольку 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. Если кто-то знает, как это сделать, не стесняйтесь редактировать/добавлять в свой пост.

Смежные вопросы